chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import type {
|
||||
GrafanaDataSourceHealthParams,
|
||||
GrafanaDataSourceHealthResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const checkDataSourceHealthTool: ToolConfig<
|
||||
GrafanaDataSourceHealthParams,
|
||||
GrafanaDataSourceHealthResponse
|
||||
> = {
|
||||
id: 'grafana_check_data_source_health',
|
||||
name: 'Grafana Check Data Source Health',
|
||||
description: 'Test connectivity to a data source by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
dataSourceUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the data source to health-check (e.g., P1234AB5678)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${params.baseUrl.replace(/\/$/, '')}/api/datasources/uid/${params.dataSourceUid.trim()}/health`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: (data.status as string) ?? 'UNKNOWN',
|
||||
message: (data.message as string) ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Health status of the data source (e.g., OK)' },
|
||||
message: { type: 'string', description: 'Detailed health message from the data source' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import type {
|
||||
GrafanaCreateAlertRuleParams,
|
||||
GrafanaCreateAlertRuleResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import { ALERT_RULE_OUTPUT_FIELDS } from '@/tools/grafana/types'
|
||||
import { mapAlertRule } from '@/tools/grafana/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createAlertRuleTool: ToolConfig<
|
||||
GrafanaCreateAlertRuleParams,
|
||||
GrafanaCreateAlertRuleResponse
|
||||
> = {
|
||||
id: 'grafana_create_alert_rule',
|
||||
name: 'Grafana Create Alert Rule',
|
||||
description: 'Create a new alert rule',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the alert rule',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the folder to create the alert in (e.g., folder-abc123)',
|
||||
},
|
||||
ruleGroup: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the rule group',
|
||||
},
|
||||
condition: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The refId of the query or expression to use as the alert condition (required for alerting rules; omit for recording rules)',
|
||||
},
|
||||
data: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON array of query/expression data objects',
|
||||
},
|
||||
forDuration: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Duration to wait before firing (e.g., 5m, 1h)',
|
||||
},
|
||||
noDataState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'State when no data is returned (NoData, Alerting, OK)',
|
||||
},
|
||||
execErrState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'State on execution error (Error, Alerting, OK)',
|
||||
},
|
||||
annotations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of annotations',
|
||||
},
|
||||
labels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of labels',
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional custom UID for the alert rule',
|
||||
},
|
||||
isPaused: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Whether the rule is paused on creation',
|
||||
},
|
||||
keepFiringFor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Duration to keep firing after the condition stops (e.g., 5m)',
|
||||
},
|
||||
missingSeriesEvalsToResolve: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of missing series evaluations before resolving',
|
||||
},
|
||||
notificationSettings: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'JSON object of per-rule notification settings (overrides)',
|
||||
},
|
||||
record: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object configuring this as a recording rule (omit for alerting rules)',
|
||||
},
|
||||
disableProvenance: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Set X-Disable-Provenance header so the rule remains editable in the Grafana UI',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/alert-rules`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
if (params.disableProvenance) {
|
||||
headers['X-Disable-Provenance'] = 'true'
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
let dataArray: unknown[] = []
|
||||
try {
|
||||
dataArray = JSON.parse(params.data)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for data parameter')
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
title: params.title,
|
||||
folderUID: params.folderUid,
|
||||
ruleGroup: params.ruleGroup,
|
||||
data: dataArray,
|
||||
}
|
||||
if (params.organizationId) body.orgID = Number(params.organizationId)
|
||||
|
||||
if (params.condition) body.condition = params.condition
|
||||
if (params.uid) body.uid = params.uid.trim()
|
||||
if (params.forDuration) body.for = params.forDuration
|
||||
if (params.noDataState) body.noDataState = params.noDataState
|
||||
if (params.execErrState) body.execErrState = params.execErrState
|
||||
if (params.isPaused !== undefined) body.isPaused = params.isPaused
|
||||
if (params.keepFiringFor) body.keep_firing_for = params.keepFiringFor
|
||||
if (params.missingSeriesEvalsToResolve !== undefined) {
|
||||
body.missingSeriesEvalsToResolve = params.missingSeriesEvalsToResolve
|
||||
}
|
||||
|
||||
if (params.annotations) {
|
||||
try {
|
||||
body.annotations = JSON.parse(params.annotations)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for annotations parameter')
|
||||
}
|
||||
}
|
||||
|
||||
if (params.labels) {
|
||||
try {
|
||||
body.labels = JSON.parse(params.labels)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for labels parameter')
|
||||
}
|
||||
}
|
||||
|
||||
if (params.notificationSettings) {
|
||||
try {
|
||||
body.notification_settings = JSON.parse(params.notificationSettings)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for notificationSettings parameter')
|
||||
}
|
||||
}
|
||||
|
||||
if (params.record) {
|
||||
try {
|
||||
body.record = JSON.parse(params.record)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for record parameter')
|
||||
}
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return { success: true, output: mapAlertRule(data) }
|
||||
},
|
||||
|
||||
outputs: ALERT_RULE_OUTPUT_FIELDS,
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import type {
|
||||
GrafanaCreateAnnotationParams,
|
||||
GrafanaCreateAnnotationResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createAnnotationTool: ToolConfig<
|
||||
GrafanaCreateAnnotationParams,
|
||||
GrafanaCreateAnnotationResponse
|
||||
> = {
|
||||
id: 'grafana_create_annotation',
|
||||
name: 'Grafana Create Annotation',
|
||||
description: 'Create an annotation on a dashboard or as a global annotation',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The text content of the annotation',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tags',
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'UID of the dashboard to add the annotation to (e.g., abc123def). Omit to create a global organization annotation.',
|
||||
},
|
||||
panelId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the panel to add the annotation to (e.g., 1, 2)',
|
||||
},
|
||||
time: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start time in epoch milliseconds (e.g., 1704067200000, defaults to now)',
|
||||
},
|
||||
timeEnd: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End time in epoch milliseconds for range annotations (e.g., 1704153600000)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/annotations`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
text: params.text,
|
||||
}
|
||||
|
||||
if (params.time) body.time = params.time
|
||||
if (params.timeEnd) body.timeEnd = params.timeEnd
|
||||
if (params.dashboardUid) body.dashboardUID = params.dashboardUid
|
||||
if (params.panelId) body.panelId = params.panelId
|
||||
|
||||
if (params.tags) {
|
||||
body.tags = params.tags
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t)
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
message: data.message || 'Annotation created successfully',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'The ID of the created annotation',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Confirmation message',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import type {
|
||||
GrafanaCreateContactPointParams,
|
||||
GrafanaCreateContactPointResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createContactPointTool: ToolConfig<
|
||||
GrafanaCreateContactPointParams,
|
||||
GrafanaCreateContactPointResponse
|
||||
> = {
|
||||
id: 'grafana_create_contact_point',
|
||||
name: 'Grafana Create Contact Point',
|
||||
description: 'Create a notification contact point (e.g., Slack, email, PagerDuty)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the contact point (groups receivers shown in the UI)',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Receiver type (e.g., slack, email, pagerduty, webhook)',
|
||||
},
|
||||
settings: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON object of type-specific settings (e.g., {"addresses":"a@b.com"} for email, {"url":"..."} for slack)',
|
||||
},
|
||||
disableResolveMessage: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Do not send a notification when the alert resolves',
|
||||
},
|
||||
disableProvenance: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Set X-Disable-Provenance header so the contact point remains editable in the UI',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/contact-points`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
if (params.disableProvenance) {
|
||||
headers['X-Disable-Provenance'] = 'true'
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
let settings: Record<string, unknown> = {}
|
||||
try {
|
||||
settings = JSON.parse(params.settings)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for settings parameter')
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
type: params.type,
|
||||
settings,
|
||||
}
|
||||
if (params.disableResolveMessage !== undefined) {
|
||||
body.disableResolveMessage = params.disableResolveMessage
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
uid: (data.uid as string) ?? '',
|
||||
name: (data.name as string) ?? '',
|
||||
type: (data.type as string) ?? '',
|
||||
settings: (data.settings as Record<string, unknown>) ?? {},
|
||||
disableResolveMessage: (data.disableResolveMessage as boolean) ?? false,
|
||||
provenance: (data.provenance as string) ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
uid: { type: 'string', description: 'UID of the created contact point' },
|
||||
name: { type: 'string', description: 'Name of the contact point' },
|
||||
type: { type: 'string', description: 'Receiver type' },
|
||||
settings: { type: 'json', description: 'Type-specific settings' },
|
||||
disableResolveMessage: {
|
||||
type: 'boolean',
|
||||
description: 'Whether resolve notifications are suppressed',
|
||||
},
|
||||
provenance: { type: 'string', description: 'Provisioning source (empty if API-managed)' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import type {
|
||||
GrafanaCreateDashboardParams,
|
||||
GrafanaCreateDashboardResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createDashboardTool: ToolConfig<
|
||||
GrafanaCreateDashboardParams,
|
||||
GrafanaCreateDashboardResponse
|
||||
> = {
|
||||
id: 'grafana_create_dashboard',
|
||||
name: 'Grafana Create Dashboard',
|
||||
description: 'Create a new dashboard',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the new dashboard',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the folder to create the dashboard in (e.g., folder-abc123)',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tags',
|
||||
},
|
||||
timezone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Dashboard timezone (e.g., browser, utc)',
|
||||
},
|
||||
refresh: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Auto-refresh interval (e.g., 5s, 1m, 5m)',
|
||||
},
|
||||
panels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON array of panel configurations',
|
||||
},
|
||||
overwrite: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Overwrite existing dashboard with same title',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit message for the dashboard version',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/dashboards/db`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const dashboard: Record<string, any> = {
|
||||
title: params.title,
|
||||
tags: params.tags
|
||||
? params.tags
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t)
|
||||
: [],
|
||||
timezone: params.timezone || 'browser',
|
||||
schemaVersion: 39,
|
||||
version: 0,
|
||||
refresh: params.refresh || '',
|
||||
}
|
||||
|
||||
if (params.panels) {
|
||||
try {
|
||||
dashboard.panels = JSON.parse(params.panels)
|
||||
} catch {
|
||||
dashboard.panels = []
|
||||
}
|
||||
} else {
|
||||
dashboard.panels = []
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
dashboard,
|
||||
overwrite: params.overwrite || false,
|
||||
}
|
||||
|
||||
if (params.folderUid) {
|
||||
body.folderUid = params.folderUid
|
||||
}
|
||||
|
||||
if (params.message) {
|
||||
body.message = params.message
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
uid: data.uid,
|
||||
url: data.url,
|
||||
status: data.status,
|
||||
version: data.version,
|
||||
slug: data.slug,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'The numeric ID of the created dashboard',
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
description: 'The UID of the created dashboard',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'The URL path to the dashboard',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Status of the operation (success)',
|
||||
},
|
||||
version: {
|
||||
type: 'number',
|
||||
description: 'The version number of the dashboard',
|
||||
},
|
||||
slug: {
|
||||
type: 'string',
|
||||
description: 'URL-friendly slug of the dashboard',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import type { GrafanaCreateFolderParams, GrafanaCreateFolderResponse } from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createFolderTool: ToolConfig<GrafanaCreateFolderParams, GrafanaCreateFolderResponse> =
|
||||
{
|
||||
id: 'grafana_create_folder',
|
||||
name: 'Grafana Create Folder',
|
||||
description: 'Create a new folder in Grafana',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the new folder',
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional UID for the folder (auto-generated if not provided)',
|
||||
},
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Parent folder UID for nested folders (requires nested folders enabled)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/folders`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
title: params.title,
|
||||
}
|
||||
|
||||
if (params.uid) body.uid = params.uid
|
||||
if (params.parentUid) body.parentUid = params.parentUid
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: (data.id as number) ?? null,
|
||||
uid: (data.uid as string) ?? null,
|
||||
title: (data.title as string) ?? null,
|
||||
url: (data.url as string) ?? null,
|
||||
parentUid: (data.parentUid as string) ?? null,
|
||||
parents: (data.parents as { uid: string; title: string; url: string }[]) ?? [],
|
||||
hasAcl: (data.hasAcl as boolean) ?? null,
|
||||
canSave: (data.canSave as boolean) ?? null,
|
||||
canEdit: (data.canEdit as boolean) ?? null,
|
||||
canAdmin: (data.canAdmin as boolean) ?? null,
|
||||
createdBy: (data.createdBy as string) ?? null,
|
||||
created: (data.created as string) ?? null,
|
||||
updatedBy: (data.updatedBy as string) ?? null,
|
||||
updated: (data.updated as string) ?? null,
|
||||
version: (data.version as number) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'The numeric ID of the created folder' },
|
||||
uid: { type: 'string', description: 'The UID of the created folder' },
|
||||
title: { type: 'string', description: 'The title of the created folder' },
|
||||
url: { type: 'string', description: 'The URL path to the folder', optional: true },
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Ancestor folder hierarchy (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
hasAcl: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the folder has custom ACL permissions',
|
||||
optional: true,
|
||||
},
|
||||
canSave: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can save the folder',
|
||||
optional: true,
|
||||
},
|
||||
canEdit: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can edit the folder',
|
||||
optional: true,
|
||||
},
|
||||
canAdmin: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user has admin rights on the folder',
|
||||
optional: true,
|
||||
},
|
||||
createdBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who created the folder',
|
||||
optional: true,
|
||||
},
|
||||
created: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was created',
|
||||
optional: true,
|
||||
},
|
||||
updatedBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who last updated the folder',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was last updated',
|
||||
optional: true,
|
||||
},
|
||||
version: { type: 'number', description: 'Version number of the folder', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
GrafanaDeleteAlertRuleParams,
|
||||
GrafanaDeleteAlertRuleResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteAlertRuleTool: ToolConfig<
|
||||
GrafanaDeleteAlertRuleParams,
|
||||
GrafanaDeleteAlertRuleResponse
|
||||
> = {
|
||||
id: 'grafana_delete_alert_rule',
|
||||
name: 'Grafana Delete Alert Rule',
|
||||
description: 'Delete an alert rule by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
alertRuleUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the alert rule to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/alert-rules/${params.alertRuleUid.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async () => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: 'Alert rule deleted successfully',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Confirmation message',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type {
|
||||
GrafanaDeleteAnnotationParams,
|
||||
GrafanaDeleteAnnotationResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteAnnotationTool: ToolConfig<
|
||||
GrafanaDeleteAnnotationParams,
|
||||
GrafanaDeleteAnnotationResponse
|
||||
> = {
|
||||
id: 'grafana_delete_annotation',
|
||||
name: 'Grafana Delete Annotation',
|
||||
description: 'Delete an annotation by its ID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
annotationId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the annotation to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/annotations/${params.annotationId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Annotation deleted successfully',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Confirmation message',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import type {
|
||||
GrafanaDeleteDashboardParams,
|
||||
GrafanaDeleteDashboardResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteDashboardTool: ToolConfig<
|
||||
GrafanaDeleteDashboardParams,
|
||||
GrafanaDeleteDashboardResponse
|
||||
> = {
|
||||
id: 'grafana_delete_dashboard',
|
||||
name: 'Grafana Delete Dashboard',
|
||||
description: 'Delete a dashboard by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the dashboard to delete (e.g., abc123def)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${params.baseUrl.replace(/\/$/, '')}/api/dashboards/uid/${params.dashboardUid.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
title: data.title || '',
|
||||
message: data.message || 'Dashboard deleted',
|
||||
id: data.id || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'The title of the deleted dashboard',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Confirmation message',
|
||||
},
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'The ID of the deleted dashboard',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { GrafanaDeleteFolderParams, GrafanaDeleteFolderResponse } from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteFolderTool: ToolConfig<GrafanaDeleteFolderParams, GrafanaDeleteFolderResponse> =
|
||||
{
|
||||
id: 'grafana_delete_folder',
|
||||
name: 'Grafana Delete Folder',
|
||||
description: 'Delete a folder by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the folder to delete (e.g., folder-abc123)',
|
||||
},
|
||||
forceDeleteRules: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Delete any alert rules stored in the folder along with it (default false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
const query = params.forceDeleteRules ? '?forceDeleteRules=true' : ''
|
||||
return `${baseUrl}/api/folders/${params.folderUid.trim()}${query}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
uid: params?.folderUid?.trim() ?? '',
|
||||
message: (data.message as string) ?? 'Folder deleted',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
uid: { type: 'string', description: 'The UID of the deleted folder' },
|
||||
message: { type: 'string', description: 'Confirmation message' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
ALERT_RULE_OUTPUT_FIELDS,
|
||||
type GrafanaGetAlertRuleParams,
|
||||
type GrafanaGetAlertRuleResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import { mapAlertRule } from '@/tools/grafana/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getAlertRuleTool: ToolConfig<GrafanaGetAlertRuleParams, GrafanaGetAlertRuleResponse> =
|
||||
{
|
||||
id: 'grafana_get_alert_rule',
|
||||
name: 'Grafana Get Alert Rule',
|
||||
description: 'Get a specific alert rule by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
alertRuleUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the alert rule to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/alert-rules/${params.alertRuleUid.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return { success: true, output: mapAlertRule(data) }
|
||||
},
|
||||
|
||||
outputs: ALERT_RULE_OUTPUT_FIELDS,
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { GrafanaGetDashboardParams, GrafanaGetDashboardResponse } from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getDashboardTool: ToolConfig<GrafanaGetDashboardParams, GrafanaGetDashboardResponse> =
|
||||
{
|
||||
id: 'grafana_get_dashboard',
|
||||
name: 'Grafana Get Dashboard',
|
||||
description: 'Get a dashboard by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the dashboard to retrieve (e.g., abc123def)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`${params.baseUrl.replace(/\/$/, '')}/api/dashboards/uid/${params.dashboardUid.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
dashboard: data.dashboard,
|
||||
meta: data.meta,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
dashboard: {
|
||||
type: 'json',
|
||||
description: 'The full dashboard JSON object',
|
||||
},
|
||||
meta: {
|
||||
type: 'json',
|
||||
description: 'Dashboard metadata (version, permissions, etc.)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type {
|
||||
GrafanaGetDataSourceParams,
|
||||
GrafanaGetDataSourceResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getDataSourceTool: ToolConfig<
|
||||
GrafanaGetDataSourceParams,
|
||||
GrafanaGetDataSourceResponse
|
||||
> = {
|
||||
id: 'grafana_get_data_source',
|
||||
name: 'Grafana Get Data Source',
|
||||
description: 'Get a data source by its ID or UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
dataSourceId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID or UID of the data source to retrieve (e.g., prometheus, P1234AB5678)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
const id = params.dataSourceId.trim()
|
||||
const isNumericId = /^\d+$/.test(id) && id.length <= 18
|
||||
if (isNumericId) {
|
||||
return `${baseUrl}/api/datasources/${id}`
|
||||
}
|
||||
return `${baseUrl}/api/datasources/uid/${id}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: (data.id as number) ?? null,
|
||||
uid: (data.uid as string) ?? null,
|
||||
orgId: (data.orgId as number) ?? null,
|
||||
name: (data.name as string) ?? null,
|
||||
type: (data.type as string) ?? null,
|
||||
typeLogoUrl: (data.typeLogoUrl as string) ?? null,
|
||||
access: (data.access as string) ?? null,
|
||||
url: (data.url as string) ?? null,
|
||||
user: (data.user as string) ?? null,
|
||||
database: (data.database as string) ?? null,
|
||||
basicAuth: (data.basicAuth as boolean) ?? false,
|
||||
basicAuthUser: (data.basicAuthUser as string) ?? null,
|
||||
withCredentials: (data.withCredentials as boolean) ?? null,
|
||||
isDefault: (data.isDefault as boolean) ?? false,
|
||||
jsonData: (data.jsonData as Record<string, unknown>) ?? {},
|
||||
secureJsonFields: (data.secureJsonFields as Record<string, boolean>) ?? {},
|
||||
version: (data.version as number) ?? null,
|
||||
readOnly: (data.readOnly as boolean) ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Data source ID' },
|
||||
uid: { type: 'string', description: 'Data source UID' },
|
||||
orgId: { type: 'number', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Data source name' },
|
||||
type: { type: 'string', description: 'Data source type' },
|
||||
typeLogoUrl: { type: 'string', description: 'Logo URL for the data source type' },
|
||||
access: { type: 'string', description: 'Access mode (proxy or direct)' },
|
||||
url: { type: 'string', description: 'Data source connection URL' },
|
||||
user: { type: 'string', description: 'Username used to connect' },
|
||||
database: { type: 'string', description: 'Database name (if applicable)' },
|
||||
basicAuth: { type: 'boolean', description: 'Whether basic auth is enabled' },
|
||||
basicAuthUser: { type: 'string', description: 'Basic auth username', optional: true },
|
||||
withCredentials: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to send credentials with cross-origin requests',
|
||||
optional: true,
|
||||
},
|
||||
isDefault: { type: 'boolean', description: 'Whether this is the default data source' },
|
||||
jsonData: { type: 'json', description: 'Additional data source configuration' },
|
||||
secureJsonFields: {
|
||||
type: 'object',
|
||||
description: 'Map of secure fields that are set (values are not returned)',
|
||||
optional: true,
|
||||
},
|
||||
version: { type: 'number', description: 'Data source version', optional: true },
|
||||
readOnly: { type: 'boolean', description: 'Whether the data source is read-only' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { GrafanaGetFolderParams, GrafanaGetFolderResponse } from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getFolderTool: ToolConfig<GrafanaGetFolderParams, GrafanaGetFolderResponse> = {
|
||||
id: 'grafana_get_folder',
|
||||
name: 'Grafana Get Folder',
|
||||
description: 'Get a folder by its UID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the folder to retrieve (e.g., folder-abc123)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/folders/${params.folderUid.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: (data.id as number) ?? null,
|
||||
uid: (data.uid as string) ?? null,
|
||||
title: (data.title as string) ?? null,
|
||||
url: (data.url as string) ?? null,
|
||||
parentUid: (data.parentUid as string) ?? null,
|
||||
parents: (data.parents as { uid: string; title: string; url: string }[]) ?? [],
|
||||
hasAcl: (data.hasAcl as boolean) ?? null,
|
||||
canSave: (data.canSave as boolean) ?? null,
|
||||
canEdit: (data.canEdit as boolean) ?? null,
|
||||
canAdmin: (data.canAdmin as boolean) ?? null,
|
||||
createdBy: (data.createdBy as string) ?? null,
|
||||
created: (data.created as string) ?? null,
|
||||
updatedBy: (data.updatedBy as string) ?? null,
|
||||
updated: (data.updated as string) ?? null,
|
||||
version: (data.version as number) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'The numeric ID of the folder' },
|
||||
uid: { type: 'string', description: 'The UID of the folder' },
|
||||
title: { type: 'string', description: 'The title of the folder' },
|
||||
url: { type: 'string', description: 'The URL path to the folder', optional: true },
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Ancestor folder hierarchy (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
hasAcl: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the folder has custom ACL permissions',
|
||||
optional: true,
|
||||
},
|
||||
canSave: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can save the folder',
|
||||
optional: true,
|
||||
},
|
||||
canEdit: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can edit the folder',
|
||||
optional: true,
|
||||
},
|
||||
canAdmin: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user has admin rights on the folder',
|
||||
optional: true,
|
||||
},
|
||||
createdBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who created the folder',
|
||||
optional: true,
|
||||
},
|
||||
created: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was created',
|
||||
optional: true,
|
||||
},
|
||||
updatedBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who last updated the folder',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was last updated',
|
||||
optional: true,
|
||||
},
|
||||
version: { type: 'number', description: 'Version number of the folder', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { GrafanaHealthCheckParams, GrafanaHealthCheckResponse } from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getHealthTool: ToolConfig<GrafanaHealthCheckParams, GrafanaHealthCheckResponse> = {
|
||||
id: 'grafana_get_health',
|
||||
name: 'Grafana Get Health',
|
||||
description: 'Check the health of the Grafana instance (version, database status)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/health`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
commit: (data.commit as string) ?? '',
|
||||
database: (data.database as string) ?? '',
|
||||
version: (data.version as string) ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
commit: { type: 'string', description: 'Git commit hash of the running Grafana build' },
|
||||
database: { type: 'string', description: 'Database health status (e.g., ok)' },
|
||||
version: { type: 'string', description: 'Grafana version' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { checkDataSourceHealthTool } from '@/tools/grafana/check_data_source_health'
|
||||
import { createAlertRuleTool } from '@/tools/grafana/create_alert_rule'
|
||||
import { createAnnotationTool } from '@/tools/grafana/create_annotation'
|
||||
import { createContactPointTool } from '@/tools/grafana/create_contact_point'
|
||||
import { createDashboardTool } from '@/tools/grafana/create_dashboard'
|
||||
import { createFolderTool } from '@/tools/grafana/create_folder'
|
||||
import { deleteAlertRuleTool } from '@/tools/grafana/delete_alert_rule'
|
||||
import { deleteAnnotationTool } from '@/tools/grafana/delete_annotation'
|
||||
import { deleteDashboardTool } from '@/tools/grafana/delete_dashboard'
|
||||
import { deleteFolderTool } from '@/tools/grafana/delete_folder'
|
||||
import { getAlertRuleTool } from '@/tools/grafana/get_alert_rule'
|
||||
import { getDashboardTool } from '@/tools/grafana/get_dashboard'
|
||||
import { getDataSourceTool } from '@/tools/grafana/get_data_source'
|
||||
import { getFolderTool } from '@/tools/grafana/get_folder'
|
||||
import { getHealthTool } from '@/tools/grafana/get_health'
|
||||
import { listAlertRulesTool } from '@/tools/grafana/list_alert_rules'
|
||||
import { listAnnotationsTool } from '@/tools/grafana/list_annotations'
|
||||
import { listContactPointsTool } from '@/tools/grafana/list_contact_points'
|
||||
import { listDashboardsTool } from '@/tools/grafana/list_dashboards'
|
||||
import { listDataSourcesTool } from '@/tools/grafana/list_data_sources'
|
||||
import { listFoldersTool } from '@/tools/grafana/list_folders'
|
||||
import { updateAlertRuleTool } from '@/tools/grafana/update_alert_rule'
|
||||
import { updateAnnotationTool } from '@/tools/grafana/update_annotation'
|
||||
import { updateDashboardTool } from '@/tools/grafana/update_dashboard'
|
||||
import { updateFolderTool } from '@/tools/grafana/update_folder'
|
||||
|
||||
export const grafanaGetDashboardTool = getDashboardTool
|
||||
export const grafanaListDashboardsTool = listDashboardsTool
|
||||
export const grafanaCreateDashboardTool = createDashboardTool
|
||||
export const grafanaUpdateDashboardTool = updateDashboardTool
|
||||
export const grafanaDeleteDashboardTool = deleteDashboardTool
|
||||
|
||||
export const grafanaListAlertRulesTool = listAlertRulesTool
|
||||
export const grafanaGetAlertRuleTool = getAlertRuleTool
|
||||
export const grafanaCreateAlertRuleTool = createAlertRuleTool
|
||||
export const grafanaUpdateAlertRuleTool = updateAlertRuleTool
|
||||
export const grafanaDeleteAlertRuleTool = deleteAlertRuleTool
|
||||
export const grafanaListContactPointsTool = listContactPointsTool
|
||||
export const grafanaCreateContactPointTool = createContactPointTool
|
||||
|
||||
export const grafanaCreateAnnotationTool = createAnnotationTool
|
||||
export const grafanaListAnnotationsTool = listAnnotationsTool
|
||||
export const grafanaUpdateAnnotationTool = updateAnnotationTool
|
||||
export const grafanaDeleteAnnotationTool = deleteAnnotationTool
|
||||
|
||||
export const grafanaListDataSourcesTool = listDataSourcesTool
|
||||
export const grafanaGetDataSourceTool = getDataSourceTool
|
||||
export const grafanaCheckDataSourceHealthTool = checkDataSourceHealthTool
|
||||
|
||||
export const grafanaListFoldersTool = listFoldersTool
|
||||
export const grafanaCreateFolderTool = createFolderTool
|
||||
export const grafanaGetFolderTool = getFolderTool
|
||||
export const grafanaUpdateFolderTool = updateFolderTool
|
||||
export const grafanaDeleteFolderTool = deleteFolderTool
|
||||
|
||||
export const grafanaGetHealthTool = getHealthTool
|
||||
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
ALERT_RULE_OUTPUT_FIELDS,
|
||||
type GrafanaListAlertRulesParams,
|
||||
type GrafanaListAlertRulesResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import { mapAlertRule } from '@/tools/grafana/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listAlertRulesTool: ToolConfig<
|
||||
GrafanaListAlertRulesParams,
|
||||
GrafanaListAlertRulesResponse
|
||||
> = {
|
||||
id: 'grafana_list_alert_rules',
|
||||
name: 'Grafana List Alert Rules',
|
||||
description: 'List all alert rules in the Grafana instance',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/alert-rules`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
rules: Array.isArray(data)
|
||||
? data.map((rule: Record<string, unknown>) => mapAlertRule(rule))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
rules: {
|
||||
type: 'array',
|
||||
description: 'List of alert rules',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: ALERT_RULE_OUTPUT_FIELDS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import type {
|
||||
GrafanaListAnnotationsParams,
|
||||
GrafanaListAnnotationsResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listAnnotationsTool: ToolConfig<
|
||||
GrafanaListAnnotationsParams,
|
||||
GrafanaListAnnotationsResponse
|
||||
> = {
|
||||
id: 'grafana_list_annotations',
|
||||
name: 'Grafana List Annotations',
|
||||
description: 'Query annotations by time range, dashboard, or tags',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
from: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start time in epoch milliseconds (e.g., 1704067200000)',
|
||||
},
|
||||
to: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End time in epoch milliseconds (e.g., 1704153600000)',
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Dashboard UID to query annotations from (e.g., abc123def). Omit to query annotations across the organization.',
|
||||
},
|
||||
dashboardId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Legacy numeric dashboard ID filter (prefer dashboardUid)',
|
||||
},
|
||||
panelId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by panel ID (e.g., 1, 2)',
|
||||
},
|
||||
alertId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by alert ID',
|
||||
},
|
||||
userId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by ID of the user who created the annotation',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tags to filter by',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Filter by type (alert or annotation)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of annotations to return',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
const searchParams = new URLSearchParams()
|
||||
|
||||
if (params.from) searchParams.set('from', String(params.from))
|
||||
if (params.to) searchParams.set('to', String(params.to))
|
||||
if (params.dashboardUid) searchParams.set('dashboardUID', params.dashboardUid)
|
||||
if (params.dashboardId) searchParams.set('dashboardId', String(params.dashboardId))
|
||||
if (params.panelId) searchParams.set('panelId', String(params.panelId))
|
||||
if (params.alertId) searchParams.set('alertId', String(params.alertId))
|
||||
if (params.userId) searchParams.set('userId', String(params.userId))
|
||||
if (params.tags) {
|
||||
params.tags.split(',').forEach((t) => searchParams.append('tags', t.trim()))
|
||||
}
|
||||
if (params.type) searchParams.set('type', params.type)
|
||||
if (params.limit) searchParams.set('limit', String(params.limit))
|
||||
|
||||
const queryString = searchParams.toString()
|
||||
return `${baseUrl}/api/annotations${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
const rawAnnotations = Array.isArray(data) ? data.flat() : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
annotations: rawAnnotations.map((a: Record<string, unknown>) => ({
|
||||
id: (a.id as number) ?? null,
|
||||
alertId: (a.alertId as number) ?? null,
|
||||
dashboardId: (a.dashboardId as number) ?? null,
|
||||
dashboardUID: (a.dashboardUID as string) ?? null,
|
||||
panelId: (a.panelId as number) ?? null,
|
||||
userId: (a.userId as number) ?? null,
|
||||
userName: (a.userName as string) ?? null,
|
||||
newState: (a.newState as string) ?? null,
|
||||
prevState: (a.prevState as string) ?? null,
|
||||
time: (a.time as number) ?? null,
|
||||
timeEnd: (a.timeEnd as number) ?? null,
|
||||
text: (a.text as string) ?? null,
|
||||
metric: (a.metric as string) ?? null,
|
||||
tags: (a.tags as string[]) ?? [],
|
||||
data: (a.data as Record<string, unknown>) ?? {},
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
annotations: {
|
||||
type: 'array',
|
||||
description: 'List of annotations',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Annotation ID' },
|
||||
alertId: { type: 'number', description: 'Associated alert ID (0 if not alert-driven)' },
|
||||
dashboardId: { type: 'number', description: 'Dashboard ID', optional: true },
|
||||
dashboardUID: { type: 'string', description: 'Dashboard UID', optional: true },
|
||||
panelId: { type: 'number', description: 'Panel ID within the dashboard', optional: true },
|
||||
userId: { type: 'number', description: 'ID of the user who created the annotation' },
|
||||
userName: {
|
||||
type: 'string',
|
||||
description: 'Username of the user who created the annotation',
|
||||
optional: true,
|
||||
},
|
||||
newState: {
|
||||
type: 'string',
|
||||
description: 'New alert state (alert annotations only)',
|
||||
optional: true,
|
||||
},
|
||||
prevState: {
|
||||
type: 'string',
|
||||
description: 'Previous alert state (alert annotations only)',
|
||||
optional: true,
|
||||
},
|
||||
time: { type: 'number', description: 'Start time in epoch ms' },
|
||||
timeEnd: { type: 'number', description: 'End time in epoch ms', optional: true },
|
||||
text: { type: 'string', description: 'Annotation text' },
|
||||
metric: {
|
||||
type: 'string',
|
||||
description: 'Metric associated with the annotation',
|
||||
optional: true,
|
||||
},
|
||||
tags: { type: 'array', items: { type: 'string' }, description: 'Annotation tags' },
|
||||
data: { type: 'json', description: 'Additional annotation data object from Grafana' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import type {
|
||||
GrafanaListContactPointsParams,
|
||||
GrafanaListContactPointsResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listContactPointsTool: ToolConfig<
|
||||
GrafanaListContactPointsParams,
|
||||
GrafanaListContactPointsResponse
|
||||
> = {
|
||||
id: 'grafana_list_contact_points',
|
||||
name: 'Grafana List Contact Points',
|
||||
description: 'List all alert notification contact points',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter contact points by exact name match',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.name) searchParams.set('name', params.name)
|
||||
const queryString = searchParams.toString()
|
||||
return `${baseUrl}/api/v1/provisioning/contact-points${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
contactPoints: Array.isArray(data)
|
||||
? data.map((cp: Record<string, unknown>) => ({
|
||||
uid: (cp.uid as string) ?? null,
|
||||
name: (cp.name as string) ?? null,
|
||||
type: (cp.type as string) ?? null,
|
||||
settings: (cp.settings as Record<string, unknown>) ?? {},
|
||||
disableResolveMessage: (cp.disableResolveMessage as boolean) ?? false,
|
||||
provenance: (cp.provenance as string) ?? '',
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
contactPoints: {
|
||||
type: 'array',
|
||||
description: 'List of contact points',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uid: { type: 'string', description: 'Contact point UID' },
|
||||
name: { type: 'string', description: 'Contact point name' },
|
||||
type: { type: 'string', description: 'Notification type (email, slack, etc.)' },
|
||||
settings: { type: 'object', description: 'Type-specific settings' },
|
||||
disableResolveMessage: {
|
||||
type: 'boolean',
|
||||
description: 'Whether resolve messages are disabled',
|
||||
},
|
||||
provenance: {
|
||||
type: 'string',
|
||||
description: 'Provisioning source (empty if API-managed)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import type {
|
||||
GrafanaListDashboardsParams,
|
||||
GrafanaListDashboardsResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listDashboardsTool: ToolConfig<
|
||||
GrafanaListDashboardsParams,
|
||||
GrafanaListDashboardsResponse
|
||||
> = {
|
||||
id: 'grafana_list_dashboards',
|
||||
name: 'Grafana List Dashboards',
|
||||
description: 'Search and list all dashboards',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search query to filter dashboards by title',
|
||||
},
|
||||
tag: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by tag (comma-separated for multiple tags)',
|
||||
},
|
||||
folderUIDs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by folder UIDs (comma-separated, e.g., abc123,def456)',
|
||||
},
|
||||
dashboardUIDs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by dashboard UIDs (comma-separated, e.g., abc123,def456)',
|
||||
},
|
||||
starred: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Only return starred dashboards',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of dashboards to return (default 1000)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Page number for pagination (1-based)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
const searchParams = new URLSearchParams()
|
||||
searchParams.set('type', 'dash-db')
|
||||
|
||||
if (params.query) searchParams.set('query', params.query)
|
||||
if (params.tag) {
|
||||
params.tag.split(',').forEach((t) => searchParams.append('tag', t.trim()))
|
||||
}
|
||||
if (params.folderUIDs) {
|
||||
params.folderUIDs.split(',').forEach((uid) => searchParams.append('folderUIDs', uid.trim()))
|
||||
}
|
||||
if (params.dashboardUIDs) {
|
||||
params.dashboardUIDs
|
||||
.split(',')
|
||||
.forEach((uid) => searchParams.append('dashboardUIDs', uid.trim()))
|
||||
}
|
||||
if (params.starred) searchParams.set('starred', 'true')
|
||||
if (params.limit) searchParams.set('limit', String(params.limit))
|
||||
if (params.page) searchParams.set('page', String(params.page))
|
||||
|
||||
return `${baseUrl}/api/search?${searchParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
dashboards: Array.isArray(data)
|
||||
? data.map((d: Record<string, unknown>) => ({
|
||||
id: (d.id as number) ?? null,
|
||||
uid: (d.uid as string) ?? null,
|
||||
title: (d.title as string) ?? null,
|
||||
uri: (d.uri as string) ?? null,
|
||||
url: (d.url as string) ?? null,
|
||||
type: (d.type as string) ?? null,
|
||||
tags: (d.tags as string[]) ?? [],
|
||||
isStarred: (d.isStarred as boolean) ?? false,
|
||||
folderId: (d.folderId as number) ?? null,
|
||||
folderUid: (d.folderUid as string) ?? null,
|
||||
folderTitle: (d.folderTitle as string) ?? null,
|
||||
folderUrl: (d.folderUrl as string) ?? null,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
dashboards: {
|
||||
type: 'array',
|
||||
description: 'List of dashboard search results',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Dashboard ID' },
|
||||
uid: { type: 'string', description: 'Dashboard UID' },
|
||||
title: { type: 'string', description: 'Dashboard title' },
|
||||
url: { type: 'string', description: 'Dashboard URL path' },
|
||||
tags: { type: 'array', description: 'Dashboard tags' },
|
||||
folderTitle: { type: 'string', description: 'Parent folder title' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import type {
|
||||
GrafanaListDataSourcesParams,
|
||||
GrafanaListDataSourcesResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listDataSourcesTool: ToolConfig<
|
||||
GrafanaListDataSourcesParams,
|
||||
GrafanaListDataSourcesResponse
|
||||
> = {
|
||||
id: 'grafana_list_data_sources',
|
||||
name: 'Grafana List Data Sources',
|
||||
description: 'List all data sources configured in Grafana',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/datasources`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
dataSources: Array.isArray(data)
|
||||
? data.map((ds: Record<string, unknown>) => ({
|
||||
id: (ds.id as number) ?? null,
|
||||
uid: (ds.uid as string) ?? null,
|
||||
orgId: (ds.orgId as number) ?? null,
|
||||
name: (ds.name as string) ?? null,
|
||||
type: (ds.type as string) ?? null,
|
||||
typeLogoUrl: (ds.typeLogoUrl as string) ?? null,
|
||||
access: (ds.access as string) ?? null,
|
||||
url: (ds.url as string) ?? null,
|
||||
user: (ds.user as string) ?? null,
|
||||
database: (ds.database as string) ?? null,
|
||||
basicAuth: (ds.basicAuth as boolean) ?? false,
|
||||
basicAuthUser: (ds.basicAuthUser as string) ?? null,
|
||||
withCredentials: (ds.withCredentials as boolean) ?? null,
|
||||
isDefault: (ds.isDefault as boolean) ?? false,
|
||||
jsonData: (ds.jsonData as Record<string, unknown>) ?? {},
|
||||
secureJsonFields: (ds.secureJsonFields as Record<string, boolean>) ?? {},
|
||||
version: (ds.version as number) ?? null,
|
||||
readOnly: (ds.readOnly as boolean) ?? false,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
dataSources: {
|
||||
type: 'array',
|
||||
description: 'List of data sources',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Data source ID' },
|
||||
uid: { type: 'string', description: 'Data source UID' },
|
||||
orgId: { type: 'number', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Data source name' },
|
||||
type: { type: 'string', description: 'Data source type (prometheus, mysql, etc.)' },
|
||||
typeLogoUrl: { type: 'string', description: 'Logo URL for the data source type' },
|
||||
access: { type: 'string', description: 'Access mode (proxy or direct)' },
|
||||
url: { type: 'string', description: 'Data source URL' },
|
||||
user: { type: 'string', description: 'Username used to connect' },
|
||||
database: { type: 'string', description: 'Database name (if applicable)' },
|
||||
basicAuth: { type: 'boolean', description: 'Whether basic auth is enabled' },
|
||||
basicAuthUser: {
|
||||
type: 'string',
|
||||
description: 'Basic auth username',
|
||||
optional: true,
|
||||
},
|
||||
withCredentials: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to send credentials with cross-origin requests',
|
||||
optional: true,
|
||||
},
|
||||
isDefault: { type: 'boolean', description: 'Whether this is the default data source' },
|
||||
jsonData: { type: 'object', description: 'Type-specific JSON configuration' },
|
||||
secureJsonFields: {
|
||||
type: 'object',
|
||||
description: 'Map of secure fields that are set (values are not returned)',
|
||||
optional: true,
|
||||
},
|
||||
version: { type: 'number', description: 'Data source version', optional: true },
|
||||
readOnly: { type: 'boolean', description: 'Whether the data source is read-only' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import type { GrafanaListFoldersParams, GrafanaListFoldersResponse } from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listFoldersTool: ToolConfig<GrafanaListFoldersParams, GrafanaListFoldersResponse> = {
|
||||
id: 'grafana_list_folders',
|
||||
name: 'Grafana List Folders',
|
||||
description: 'List all folders in Grafana',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of folders to return',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Page number for pagination',
|
||||
},
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'List children of this folder UID (requires nested folders enabled)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
const searchParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) searchParams.set('limit', String(params.limit))
|
||||
if (params.page) searchParams.set('page', String(params.page))
|
||||
if (params.parentUid) searchParams.set('parentUid', params.parentUid.trim())
|
||||
|
||||
const queryString = searchParams.toString()
|
||||
return `${baseUrl}/api/folders${queryString ? `?${queryString}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
folders: Array.isArray(data)
|
||||
? data.map((f: Record<string, unknown>) => ({
|
||||
id: (f.id as number) ?? null,
|
||||
uid: (f.uid as string) ?? null,
|
||||
title: (f.title as string) ?? null,
|
||||
url: (f.url as string) ?? null,
|
||||
parentUid: (f.parentUid as string) ?? null,
|
||||
parents: (f.parents as { uid: string; title: string; url: string }[]) ?? [],
|
||||
hasAcl: (f.hasAcl as boolean) ?? null,
|
||||
canSave: (f.canSave as boolean) ?? null,
|
||||
canEdit: (f.canEdit as boolean) ?? null,
|
||||
canAdmin: (f.canAdmin as boolean) ?? null,
|
||||
createdBy: (f.createdBy as string) ?? null,
|
||||
created: (f.created as string) ?? null,
|
||||
updatedBy: (f.updatedBy as string) ?? null,
|
||||
updated: (f.updated as string) ?? null,
|
||||
version: (f.version as number) ?? null,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
folders: {
|
||||
type: 'array',
|
||||
description: 'List of folders',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Folder ID' },
|
||||
uid: { type: 'string', description: 'Folder UID' },
|
||||
title: { type: 'string', description: 'Folder title' },
|
||||
url: { type: 'string', description: 'Folder URL path', optional: true },
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Ancestor folder hierarchy (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
hasAcl: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the folder has custom ACL permissions',
|
||||
optional: true,
|
||||
},
|
||||
canSave: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can save the folder',
|
||||
optional: true,
|
||||
},
|
||||
canEdit: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can edit the folder',
|
||||
optional: true,
|
||||
},
|
||||
canAdmin: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user has admin rights',
|
||||
optional: true,
|
||||
},
|
||||
createdBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who created the folder',
|
||||
optional: true,
|
||||
},
|
||||
created: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was created',
|
||||
optional: true,
|
||||
},
|
||||
updatedBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who last updated the folder',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was last updated',
|
||||
optional: true,
|
||||
},
|
||||
version: { type: 'number', description: 'Folder version number', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,564 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Canonical output schema fields shared across alert rule tools.
|
||||
*/
|
||||
export const ALERT_RULE_OUTPUT_FIELDS: Record<string, OutputProperty> = {
|
||||
id: { type: 'number', description: 'Alert rule numeric ID', optional: true },
|
||||
uid: { type: 'string', description: 'Alert rule UID' },
|
||||
title: { type: 'string', description: 'Alert rule title' },
|
||||
condition: { type: 'string', description: 'RefId of the query used as the alert condition' },
|
||||
data: { type: 'json', description: 'Alert rule query/expression data array' },
|
||||
updated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
noDataState: { type: 'string', description: 'State when no data is returned' },
|
||||
execErrState: { type: 'string', description: 'State on execution error' },
|
||||
for: { type: 'string', description: 'Duration the condition must hold before firing' },
|
||||
keepFiringFor: {
|
||||
type: 'string',
|
||||
description: 'Duration to keep firing after condition stops',
|
||||
optional: true,
|
||||
},
|
||||
missingSeriesEvalsToResolve: {
|
||||
type: 'number',
|
||||
description: 'Number of missing series evaluations before resolving',
|
||||
optional: true,
|
||||
},
|
||||
annotations: { type: 'json', description: 'Alert annotations' },
|
||||
labels: { type: 'json', description: 'Alert labels' },
|
||||
isPaused: { type: 'boolean', description: 'Whether the rule is paused' },
|
||||
folderUID: { type: 'string', description: 'Parent folder UID' },
|
||||
ruleGroup: { type: 'string', description: 'Rule group name' },
|
||||
orgID: { type: 'number', description: 'Organization ID' },
|
||||
provenance: { type: 'string', description: 'Provisioning source (empty if API-managed)' },
|
||||
notification_settings: {
|
||||
type: 'json',
|
||||
description: 'Per-rule notification settings (overrides)',
|
||||
optional: true,
|
||||
},
|
||||
record: {
|
||||
type: 'json',
|
||||
description: 'Recording rule configuration (recording rules only)',
|
||||
optional: true,
|
||||
},
|
||||
}
|
||||
|
||||
interface GrafanaBaseParams {
|
||||
apiKey: string
|
||||
baseUrl: string
|
||||
organizationId?: string
|
||||
}
|
||||
|
||||
export interface GrafanaHealthCheckParams extends GrafanaBaseParams {}
|
||||
|
||||
export interface GrafanaHealthCheckResponse extends ToolResponse {
|
||||
output: {
|
||||
commit: string
|
||||
database: string
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaDataSourceHealthParams extends GrafanaBaseParams {
|
||||
dataSourceUid: string
|
||||
}
|
||||
|
||||
export interface GrafanaDataSourceHealthResponse extends ToolResponse {
|
||||
output: {
|
||||
status: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaGetDashboardParams extends GrafanaBaseParams {
|
||||
dashboardUid: string
|
||||
}
|
||||
|
||||
interface GrafanaDashboardMeta {
|
||||
type: string
|
||||
canSave: boolean
|
||||
canEdit: boolean
|
||||
canAdmin: boolean
|
||||
canStar: boolean
|
||||
canDelete: boolean
|
||||
slug: string
|
||||
url: string
|
||||
expires: string
|
||||
created: string
|
||||
updated: string
|
||||
updatedBy: string
|
||||
createdBy: string
|
||||
version: number
|
||||
hasAcl: boolean
|
||||
isFolder: boolean
|
||||
folderId: number
|
||||
folderUid: string
|
||||
folderTitle: string
|
||||
folderUrl: string
|
||||
provisioned: boolean
|
||||
provisionedExternalId: string
|
||||
}
|
||||
|
||||
interface GrafanaDashboard {
|
||||
id: number
|
||||
uid: string
|
||||
title: string
|
||||
tags: string[]
|
||||
timezone: string
|
||||
schemaVersion: number
|
||||
version: number
|
||||
refresh: string
|
||||
panels: Record<string, unknown>[]
|
||||
templating: Record<string, unknown>
|
||||
annotations: Record<string, unknown>
|
||||
time: {
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaGetDashboardResponse extends ToolResponse {
|
||||
output: {
|
||||
dashboard: GrafanaDashboard
|
||||
meta: GrafanaDashboardMeta
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaListDashboardsParams extends GrafanaBaseParams {
|
||||
query?: string
|
||||
tag?: string
|
||||
folderUIDs?: string
|
||||
dashboardUIDs?: string
|
||||
starred?: boolean
|
||||
limit?: number
|
||||
page?: number
|
||||
}
|
||||
|
||||
interface GrafanaDashboardSearchResult {
|
||||
id: number | null
|
||||
uid: string | null
|
||||
title: string | null
|
||||
uri: string | null
|
||||
url: string | null
|
||||
type: string | null
|
||||
tags: string[]
|
||||
isStarred: boolean
|
||||
folderId: number | null
|
||||
folderUid: string | null
|
||||
folderTitle: string | null
|
||||
folderUrl: string | null
|
||||
}
|
||||
|
||||
export interface GrafanaListDashboardsResponse extends ToolResponse {
|
||||
output: {
|
||||
dashboards: GrafanaDashboardSearchResult[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaCreateDashboardParams extends GrafanaBaseParams {
|
||||
title: string
|
||||
folderUid?: string
|
||||
tags?: string
|
||||
timezone?: string
|
||||
refresh?: string
|
||||
panels?: string
|
||||
overwrite?: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface GrafanaCreateDashboardResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
uid: string
|
||||
url: string
|
||||
status: string
|
||||
version: number
|
||||
slug: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaUpdateDashboardParams extends GrafanaBaseParams {
|
||||
dashboardUid: string
|
||||
title?: string
|
||||
folderUid?: string
|
||||
tags?: string
|
||||
timezone?: string
|
||||
refresh?: string
|
||||
panels?: string
|
||||
overwrite?: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
interface GrafanaUpdateDashboardResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
uid: string
|
||||
url: string
|
||||
status: string
|
||||
version: number
|
||||
slug: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteDashboardParams extends GrafanaBaseParams {
|
||||
dashboardUid: string
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteDashboardResponse extends ToolResponse {
|
||||
output: {
|
||||
title: string
|
||||
message: string
|
||||
id: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaListAlertRulesParams extends GrafanaBaseParams {}
|
||||
|
||||
interface GrafanaAlertRule {
|
||||
id: number | null
|
||||
uid: string | null
|
||||
title: string | null
|
||||
condition: string | null
|
||||
data: unknown[]
|
||||
updated: string | null
|
||||
noDataState: string | null
|
||||
execErrState: string | null
|
||||
for: string | null
|
||||
keepFiringFor: string | null
|
||||
missingSeriesEvalsToResolve: number | null
|
||||
annotations: Record<string, string>
|
||||
labels: Record<string, string>
|
||||
isPaused: boolean
|
||||
folderUID: string | null
|
||||
ruleGroup: string | null
|
||||
orgID: number | null
|
||||
provenance: string
|
||||
notification_settings: Record<string, unknown> | null
|
||||
record: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export interface GrafanaListAlertRulesResponse extends ToolResponse {
|
||||
output: {
|
||||
rules: GrafanaAlertRule[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaGetAlertRuleParams extends GrafanaBaseParams {
|
||||
alertRuleUid: string
|
||||
}
|
||||
|
||||
export interface GrafanaGetAlertRuleResponse extends ToolResponse {
|
||||
output: GrafanaAlertRule
|
||||
}
|
||||
|
||||
export interface GrafanaCreateAlertRuleParams extends GrafanaBaseParams {
|
||||
title: string
|
||||
folderUid: string
|
||||
ruleGroup: string
|
||||
condition?: string
|
||||
data: string
|
||||
forDuration?: string
|
||||
noDataState?: string
|
||||
execErrState?: string
|
||||
annotations?: string
|
||||
labels?: string
|
||||
uid?: string
|
||||
isPaused?: boolean
|
||||
keepFiringFor?: string
|
||||
missingSeriesEvalsToResolve?: number
|
||||
notificationSettings?: string
|
||||
record?: string
|
||||
disableProvenance?: boolean
|
||||
}
|
||||
|
||||
export interface GrafanaCreateAlertRuleResponse extends ToolResponse {
|
||||
output: GrafanaAlertRule
|
||||
}
|
||||
|
||||
export interface GrafanaUpdateAlertRuleParams extends GrafanaBaseParams {
|
||||
alertRuleUid: string
|
||||
title?: string
|
||||
folderUid?: string
|
||||
ruleGroup?: string
|
||||
condition?: string
|
||||
data?: string
|
||||
forDuration?: string
|
||||
noDataState?: string
|
||||
execErrState?: string
|
||||
annotations?: string
|
||||
labels?: string
|
||||
isPaused?: boolean
|
||||
keepFiringFor?: string
|
||||
missingSeriesEvalsToResolve?: number
|
||||
notificationSettings?: string
|
||||
record?: string
|
||||
disableProvenance?: boolean
|
||||
}
|
||||
|
||||
interface GrafanaUpdateAlertRuleResponse extends ToolResponse {
|
||||
output: GrafanaAlertRule
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteAlertRuleParams extends GrafanaBaseParams {
|
||||
alertRuleUid: string
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteAlertRuleResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaCreateAnnotationParams extends GrafanaBaseParams {
|
||||
text: string
|
||||
tags?: string
|
||||
dashboardUid?: string
|
||||
panelId?: number
|
||||
time?: number
|
||||
timeEnd?: number
|
||||
}
|
||||
|
||||
interface GrafanaAnnotation {
|
||||
id: number | null
|
||||
alertId: number | null
|
||||
dashboardId: number | null
|
||||
dashboardUID: string | null
|
||||
panelId: number | null
|
||||
userId: number | null
|
||||
userName: string | null
|
||||
newState: string | null
|
||||
prevState: string | null
|
||||
time: number | null
|
||||
timeEnd: number | null
|
||||
text: string | null
|
||||
metric: string | null
|
||||
tags: string[]
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GrafanaCreateAnnotationResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaListAnnotationsParams extends GrafanaBaseParams {
|
||||
from?: number
|
||||
to?: number
|
||||
dashboardId?: number
|
||||
dashboardUid?: string
|
||||
panelId?: number
|
||||
alertId?: number
|
||||
userId?: number
|
||||
tags?: string
|
||||
type?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface GrafanaListAnnotationsResponse extends ToolResponse {
|
||||
output: {
|
||||
annotations: GrafanaAnnotation[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaUpdateAnnotationParams extends GrafanaBaseParams {
|
||||
annotationId: number
|
||||
text?: string
|
||||
tags?: string
|
||||
time?: number
|
||||
timeEnd?: number
|
||||
}
|
||||
|
||||
export interface GrafanaUpdateAnnotationResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteAnnotationParams extends GrafanaBaseParams {
|
||||
annotationId: number
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteAnnotationResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaListDataSourcesParams extends GrafanaBaseParams {}
|
||||
|
||||
interface GrafanaDataSource {
|
||||
id: number
|
||||
uid: string
|
||||
orgId: number
|
||||
name: string
|
||||
type: string
|
||||
typeLogoUrl: string
|
||||
access: string
|
||||
url: string
|
||||
user: string
|
||||
database: string
|
||||
basicAuth: boolean
|
||||
basicAuthUser?: string
|
||||
withCredentials?: boolean
|
||||
isDefault: boolean
|
||||
jsonData: Record<string, unknown>
|
||||
secureJsonFields?: Record<string, boolean>
|
||||
version?: number
|
||||
readOnly: boolean
|
||||
}
|
||||
|
||||
export interface GrafanaListDataSourcesResponse extends ToolResponse {
|
||||
output: {
|
||||
dataSources: GrafanaDataSource[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaGetDataSourceParams extends GrafanaBaseParams {
|
||||
dataSourceId: string
|
||||
}
|
||||
|
||||
export interface GrafanaGetDataSourceResponse extends ToolResponse {
|
||||
output: GrafanaDataSource
|
||||
}
|
||||
|
||||
export interface GrafanaListFoldersParams extends GrafanaBaseParams {
|
||||
limit?: number
|
||||
page?: number
|
||||
parentUid?: string
|
||||
}
|
||||
|
||||
interface GrafanaFolderParent {
|
||||
uid: string
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface GrafanaFolder {
|
||||
id: number
|
||||
uid: string
|
||||
title: string
|
||||
url?: string
|
||||
hasAcl?: boolean
|
||||
canSave?: boolean
|
||||
canEdit?: boolean
|
||||
canAdmin?: boolean
|
||||
createdBy?: string
|
||||
created?: string
|
||||
updatedBy?: string
|
||||
updated?: string
|
||||
version?: number
|
||||
parentUid?: string | null
|
||||
parents?: GrafanaFolderParent[]
|
||||
}
|
||||
|
||||
export interface GrafanaListFoldersResponse extends ToolResponse {
|
||||
output: {
|
||||
folders: GrafanaFolder[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaCreateFolderParams extends GrafanaBaseParams {
|
||||
title: string
|
||||
uid?: string
|
||||
parentUid?: string
|
||||
}
|
||||
|
||||
export interface GrafanaCreateFolderResponse extends ToolResponse {
|
||||
output: GrafanaFolder
|
||||
}
|
||||
|
||||
export interface GrafanaGetFolderParams extends GrafanaBaseParams {
|
||||
folderUid: string
|
||||
}
|
||||
|
||||
export interface GrafanaGetFolderResponse extends ToolResponse {
|
||||
output: GrafanaFolder
|
||||
}
|
||||
|
||||
export interface GrafanaUpdateFolderParams extends GrafanaBaseParams {
|
||||
folderUid: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export interface GrafanaUpdateFolderResponse extends ToolResponse {
|
||||
output: GrafanaFolder
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteFolderParams extends GrafanaBaseParams {
|
||||
folderUid: string
|
||||
forceDeleteRules?: boolean
|
||||
}
|
||||
|
||||
export interface GrafanaDeleteFolderResponse extends ToolResponse {
|
||||
output: {
|
||||
uid: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaListContactPointsParams extends GrafanaBaseParams {
|
||||
name?: string
|
||||
}
|
||||
|
||||
interface GrafanaContactPoint {
|
||||
uid: string
|
||||
name: string
|
||||
type: string
|
||||
settings: Record<string, unknown>
|
||||
disableResolveMessage: boolean
|
||||
provenance: string
|
||||
}
|
||||
|
||||
export interface GrafanaListContactPointsResponse extends ToolResponse {
|
||||
output: {
|
||||
contactPoints: GrafanaContactPoint[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrafanaCreateContactPointParams extends GrafanaBaseParams {
|
||||
name: string
|
||||
type: string
|
||||
settings: string
|
||||
disableResolveMessage?: boolean
|
||||
disableProvenance?: boolean
|
||||
}
|
||||
|
||||
export interface GrafanaCreateContactPointResponse extends ToolResponse {
|
||||
output: {
|
||||
uid: string
|
||||
name: string
|
||||
type: string
|
||||
settings: Record<string, unknown>
|
||||
disableResolveMessage: boolean
|
||||
provenance: string
|
||||
}
|
||||
}
|
||||
|
||||
export type GrafanaResponse =
|
||||
| GrafanaHealthCheckResponse
|
||||
| GrafanaDataSourceHealthResponse
|
||||
| GrafanaGetDashboardResponse
|
||||
| GrafanaListDashboardsResponse
|
||||
| GrafanaCreateDashboardResponse
|
||||
| GrafanaUpdateDashboardResponse
|
||||
| GrafanaDeleteDashboardResponse
|
||||
| GrafanaListAlertRulesResponse
|
||||
| GrafanaGetAlertRuleResponse
|
||||
| GrafanaCreateAlertRuleResponse
|
||||
| GrafanaUpdateAlertRuleResponse
|
||||
| GrafanaDeleteAlertRuleResponse
|
||||
| GrafanaCreateAnnotationResponse
|
||||
| GrafanaListAnnotationsResponse
|
||||
| GrafanaUpdateAnnotationResponse
|
||||
| GrafanaDeleteAnnotationResponse
|
||||
| GrafanaListDataSourcesResponse
|
||||
| GrafanaGetDataSourceResponse
|
||||
| GrafanaListFoldersResponse
|
||||
| GrafanaCreateFolderResponse
|
||||
| GrafanaGetFolderResponse
|
||||
| GrafanaUpdateFolderResponse
|
||||
| GrafanaDeleteFolderResponse
|
||||
| GrafanaListContactPointsResponse
|
||||
| GrafanaCreateContactPointResponse
|
||||
@@ -0,0 +1,171 @@
|
||||
import { ALERT_RULE_OUTPUT_FIELDS, type GrafanaUpdateAlertRuleParams } from '@/tools/grafana/types'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolResponse> = {
|
||||
id: 'grafana_update_alert_rule',
|
||||
name: 'Grafana Update Alert Rule',
|
||||
description: 'Update an existing alert rule. Fetches the current rule and merges your changes.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
alertRuleUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the alert rule to update',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New title for the alert rule',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New folder UID to move the alert to (e.g., folder-abc123)',
|
||||
},
|
||||
ruleGroup: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New rule group name',
|
||||
},
|
||||
condition: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New condition refId',
|
||||
},
|
||||
data: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New JSON array of query/expression data objects',
|
||||
},
|
||||
forDuration: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Duration to wait before firing (e.g., 5m, 1h)',
|
||||
},
|
||||
noDataState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'State when no data is returned (NoData, Alerting, OK)',
|
||||
},
|
||||
execErrState: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'State on execution error (Error, Alerting, OK)',
|
||||
},
|
||||
annotations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of annotations',
|
||||
},
|
||||
labels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of labels',
|
||||
},
|
||||
isPaused: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Whether the rule is paused',
|
||||
},
|
||||
keepFiringFor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Duration to keep firing after the condition stops (e.g., 5m)',
|
||||
},
|
||||
missingSeriesEvalsToResolve: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Number of missing series evaluations before resolving',
|
||||
},
|
||||
notificationSettings: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'JSON object of per-rule notification settings (overrides)',
|
||||
},
|
||||
record: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object configuring this as a recording rule',
|
||||
},
|
||||
disableProvenance: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Set X-Disable-Provenance header so the rule remains editable in the Grafana UI',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => '/api/tools/grafana/update_alert_rule',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
baseUrl: params.baseUrl,
|
||||
organizationId: params.organizationId,
|
||||
alertRuleUid: params.alertRuleUid,
|
||||
title: params.title,
|
||||
folderUid: params.folderUid,
|
||||
ruleGroup: params.ruleGroup,
|
||||
condition: params.condition,
|
||||
data: params.data,
|
||||
forDuration: params.forDuration,
|
||||
noDataState: params.noDataState,
|
||||
execErrState: params.execErrState,
|
||||
annotations: params.annotations,
|
||||
labels: params.labels,
|
||||
isPaused: params.isPaused,
|
||||
keepFiringFor: params.keepFiringFor,
|
||||
missingSeriesEvalsToResolve: params.missingSeriesEvalsToResolve,
|
||||
notificationSettings: params.notificationSettings,
|
||||
record: params.record,
|
||||
disableProvenance: params.disableProvenance,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: data.success ?? true,
|
||||
output: data.output ?? {},
|
||||
...(data.error ? { error: data.error } : {}),
|
||||
}
|
||||
},
|
||||
|
||||
outputs: ALERT_RULE_OUTPUT_FIELDS,
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import type {
|
||||
GrafanaUpdateAnnotationParams,
|
||||
GrafanaUpdateAnnotationResponse,
|
||||
} from '@/tools/grafana/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateAnnotationTool: ToolConfig<
|
||||
GrafanaUpdateAnnotationParams,
|
||||
GrafanaUpdateAnnotationResponse
|
||||
> = {
|
||||
id: 'grafana_update_annotation',
|
||||
name: 'Grafana Update Annotation',
|
||||
description: 'Update an existing annotation',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
annotationId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the annotation to update',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New text content for the annotation (PATCH supports partial updates)',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of new tags',
|
||||
},
|
||||
time: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New start time in epoch milliseconds (e.g., 1704067200000)',
|
||||
},
|
||||
timeEnd: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New end time in epoch milliseconds (e.g., 1704153600000)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${params.baseUrl.replace(/\/$/, '')}/api/annotations/${params.annotationId}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (params.text !== undefined) body.text = params.text
|
||||
if (params.time) body.time = params.time
|
||||
if (params.timeEnd) body.timeEnd = params.timeEnd
|
||||
|
||||
if (params.tags) {
|
||||
body.tags = params.tags
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t)
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id || 0,
|
||||
message: data.message || 'Annotation updated successfully',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'The ID of the updated annotation',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Confirmation message',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import type { GrafanaUpdateDashboardParams } from '@/tools/grafana/types'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
export const updateDashboardTool: ToolConfig<GrafanaUpdateDashboardParams, ToolResponse> = {
|
||||
id: 'grafana_update_dashboard',
|
||||
name: 'Grafana Update Dashboard',
|
||||
description:
|
||||
'Update an existing dashboard. Fetches the current dashboard and merges your changes.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the dashboard to update (e.g., abc123def)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New title for the dashboard',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New folder UID to move the dashboard to (e.g., folder-abc123)',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of new tags',
|
||||
},
|
||||
timezone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Dashboard timezone (e.g., browser, utc)',
|
||||
},
|
||||
refresh: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Auto-refresh interval (e.g., 5s, 1m, 5m)',
|
||||
},
|
||||
panels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON array of panel configurations',
|
||||
},
|
||||
overwrite: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Overwrite even if there is a version conflict (defaults to false to surface 412 conflicts)',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit message for this version',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => '/api/tools/grafana/update_dashboard',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
baseUrl: params.baseUrl,
|
||||
organizationId: params.organizationId,
|
||||
dashboardUid: params.dashboardUid,
|
||||
title: params.title,
|
||||
folderUid: params.folderUid,
|
||||
tags: params.tags,
|
||||
timezone: params.timezone,
|
||||
refresh: params.refresh,
|
||||
panels: params.panels,
|
||||
overwrite: params.overwrite,
|
||||
message: params.message,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: data.success ?? true,
|
||||
output: data.output ?? {},
|
||||
...(data.error ? { error: data.error } : {}),
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'The numeric ID of the updated dashboard',
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
description: 'The UID of the updated dashboard',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'The URL path to the dashboard',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Status of the operation (success)',
|
||||
},
|
||||
version: {
|
||||
type: 'number',
|
||||
description: 'The new version number of the dashboard',
|
||||
},
|
||||
slug: {
|
||||
type: 'string',
|
||||
description: 'URL-friendly slug of the dashboard',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { GrafanaUpdateFolderParams } from '@/tools/grafana/types'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
export const updateFolderTool: ToolConfig<GrafanaUpdateFolderParams, ToolResponse> = {
|
||||
id: 'grafana_update_folder',
|
||||
name: 'Grafana Update Folder',
|
||||
description: 'Update (rename) a folder. Fetches the current folder and merges your changes.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana Service Account Token',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grafana instance URL (e.g., https://your-grafana.com)',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID for multi-org Grafana instances (e.g., 1, 2)',
|
||||
},
|
||||
folderUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The UID of the folder to update (e.g., folder-abc123)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New title for the folder',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => '/api/tools/grafana/update_folder',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
baseUrl: params.baseUrl,
|
||||
organizationId: params.organizationId,
|
||||
folderUid: params.folderUid,
|
||||
title: params.title,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: data.success ?? true,
|
||||
output: data.output ?? {},
|
||||
...(data.error ? { error: data.error } : {}),
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'The numeric ID of the folder' },
|
||||
uid: { type: 'string', description: 'The UID of the folder' },
|
||||
title: { type: 'string', description: 'The updated title of the folder' },
|
||||
url: { type: 'string', description: 'The URL path to the folder', optional: true },
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Ancestor folder hierarchy (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
hasAcl: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the folder has custom ACL permissions',
|
||||
optional: true,
|
||||
},
|
||||
canSave: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can save the folder',
|
||||
optional: true,
|
||||
},
|
||||
canEdit: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can edit the folder',
|
||||
optional: true,
|
||||
},
|
||||
canAdmin: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user has admin rights on the folder',
|
||||
optional: true,
|
||||
},
|
||||
createdBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who created the folder',
|
||||
optional: true,
|
||||
},
|
||||
created: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was created',
|
||||
optional: true,
|
||||
},
|
||||
updatedBy: {
|
||||
type: 'string',
|
||||
description: 'Username of who last updated the folder',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the folder was last updated',
|
||||
optional: true,
|
||||
},
|
||||
version: { type: 'number', description: 'Version number of the folder', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Map a raw Grafana ProvisionedAlertRule JSON object to the canonical output shape
|
||||
* shared across list/get/create/update alert rule tools.
|
||||
*/
|
||||
export function mapAlertRule(rule: Record<string, unknown>) {
|
||||
return {
|
||||
id: (rule.id as number) ?? null,
|
||||
uid: (rule.uid as string) ?? null,
|
||||
title: (rule.title as string) ?? null,
|
||||
condition: (rule.condition as string) ?? null,
|
||||
data: (rule.data as unknown[]) ?? [],
|
||||
updated: (rule.updated as string) ?? null,
|
||||
noDataState: (rule.noDataState as string) ?? null,
|
||||
execErrState: (rule.execErrState as string) ?? null,
|
||||
for: (rule.for as string) ?? null,
|
||||
keepFiringFor: (rule.keep_firing_for as string) ?? (rule.keepFiringFor as string) ?? null,
|
||||
missingSeriesEvalsToResolve:
|
||||
(rule.missingSeriesEvalsToResolve as number) ??
|
||||
(rule.missing_series_evals_to_resolve as number) ??
|
||||
null,
|
||||
annotations: (rule.annotations as Record<string, string>) ?? {},
|
||||
labels: (rule.labels as Record<string, string>) ?? {},
|
||||
isPaused: (rule.isPaused as boolean) ?? false,
|
||||
folderUID: (rule.folderUID as string) ?? null,
|
||||
ruleGroup: (rule.ruleGroup as string) ?? null,
|
||||
orgID: (rule.orgID as number) ?? (rule.orgId as number) ?? null,
|
||||
provenance: (rule.provenance as string) ?? '',
|
||||
notification_settings: (rule.notification_settings as Record<string, unknown>) ?? null,
|
||||
record: (rule.record as Record<string, unknown>) ?? null,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user