mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(grafana): align tools and block with Grafana API spec (#4574)
* improvement(grafana): align tools and block with official Grafana API spec Validates and corrects the Grafana integration against the official API docs: fixes wire-format field naming for provisioned alert rules (missing_series_evals_to_resolve, keepFiringFor, orgID), adds X-Disable-Provenance support, expands alert-rule params (isPaused, notificationSettings, record, annotations, labels), corrects defaults (execErrState=Error, dashboard overwrite=false), and centralizes alert-rule output mapping in a shared utils module. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(grafana): correct wire-format casing for provisioned alert rule fields Grafana's ProvisionedAlertRule schema (verified against upstream Go source and swagger spec) uses keep_firing_for (snake_case) and missingSeriesEvalsToResolve (camelCase) — the opposite of what prior audit rounds assumed. POST/PUT bodies now send the correct field names; mapAlertRule reads the correct primary names with the old casings kept as fallbacks. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(grafana): address PR review feedback - Drop hardcoded orgID: 1 fallback; only send orgID when organizationId is provided, so token-scoped org context drives rule placement. - Surface invalid JSON for notificationSettings/record on alert rule create/update instead of silently dropping the input. - Fix execErrState description in update_alert_rule to include Error. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(grafana): surface invalid JSON for annotations/labels/data on alert rules Match the behavior of other JSON params (data, notificationSettings, record): return a descriptive error instead of silently falling back to {} (create) or keeping the existing value (update). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(grafana): expose alert-rule output fields in generated docs Move ALERT_RULE_OUTPUT_FIELDS from utils.ts to types.ts and rename to SCREAMING_SNAKE_CASE so scripts/generate-docs.ts (which only resolves const references from types.ts matching [A-Z][A-Z_0-9]+) can inline the per-field rows into the generated alert-rule output tables. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
773cd84e3f
commit
c21bb915f7
@@ -71,9 +71,11 @@ Search and list all dashboards
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `query` | string | No | Search query to filter dashboards by title |
|
||||
| `tag` | string | No | Filter by tag \(comma-separated for multiple tags\) |
|
||||
| `folderIds` | string | No | Filter by folder IDs \(comma-separated, e.g., 1,2,3\) |
|
||||
| `folderUIDs` | string | No | Filter by folder UIDs \(comma-separated, e.g., abc123,def456\) |
|
||||
| `dashboardUIDs` | string | No | Filter by dashboard UIDs \(comma-separated, e.g., abc123,def456\) |
|
||||
| `starred` | boolean | No | Only return starred dashboards |
|
||||
| `limit` | number | No | Maximum number of dashboards to return |
|
||||
| `limit` | number | No | Maximum number of dashboards to return \(default 1000\) |
|
||||
| `page` | number | No | Page number for pagination \(1-based\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -136,7 +138,7 @@ Update an existing dashboard. Fetches the current dashboard and merges your chan
|
||||
| `timezone` | string | No | Dashboard timezone \(e.g., browser, utc\) |
|
||||
| `refresh` | string | No | Auto-refresh interval \(e.g., 5s, 1m, 5m\) |
|
||||
| `panels` | string | No | JSON array of panel configurations |
|
||||
| `overwrite` | boolean | No | Overwrite even if there is a version conflict |
|
||||
| `overwrite` | boolean | No | Overwrite even if there is a version conflict \(defaults to false to surface 412 conflicts\) |
|
||||
| `message` | string | No | Commit message for this version |
|
||||
|
||||
#### Output
|
||||
@@ -188,13 +190,26 @@ List all alert rules in the Grafana instance
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `rules` | array | List of alert rules |
|
||||
| ↳ `id` | number | Alert rule numeric ID |
|
||||
| ↳ `uid` | string | Alert rule UID |
|
||||
| ↳ `title` | string | Alert rule title |
|
||||
| ↳ `condition` | string | Alert condition |
|
||||
| ↳ `folderUID` | string | Parent folder UID |
|
||||
| ↳ `ruleGroup` | string | Rule group name |
|
||||
| ↳ `condition` | string | RefId of the query used as the alert condition |
|
||||
| ↳ `data` | json | Alert rule query/expression data array |
|
||||
| ↳ `updated` | string | Last update timestamp |
|
||||
| ↳ `noDataState` | string | State when no data is returned |
|
||||
| ↳ `execErrState` | string | State on execution error |
|
||||
| ↳ `for` | string | Duration the condition must hold before firing |
|
||||
| ↳ `keepFiringFor` | string | Duration to keep firing after condition stops |
|
||||
| ↳ `missingSeriesEvalsToResolve` | number | Number of missing series evaluations before resolving |
|
||||
| ↳ `annotations` | json | Alert annotations |
|
||||
| ↳ `labels` | json | Alert labels |
|
||||
| ↳ `isPaused` | boolean | Whether the rule is paused |
|
||||
| ↳ `folderUID` | string | Parent folder UID |
|
||||
| ↳ `ruleGroup` | string | Rule group name |
|
||||
| ↳ `orgID` | number | Organization ID |
|
||||
| ↳ `provenance` | string | Provisioning source \(empty if API-managed\) |
|
||||
| ↳ `notification_settings` | json | Per-rule notification settings \(overrides\) |
|
||||
| ↳ `record` | json | Recording rule configuration \(recording rules only\) |
|
||||
|
||||
### `grafana_get_alert_rule`
|
||||
|
||||
@@ -213,16 +228,26 @@ Get a specific alert rule by its UID
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | number | Alert rule numeric ID |
|
||||
| `uid` | string | Alert rule UID |
|
||||
| `title` | string | Alert rule title |
|
||||
| `condition` | string | Alert condition |
|
||||
| `data` | json | Alert rule query data |
|
||||
| `folderUID` | string | Parent folder UID |
|
||||
| `ruleGroup` | string | Rule group name |
|
||||
| `condition` | string | RefId of the query used as the alert condition |
|
||||
| `data` | json | Alert rule query/expression data array |
|
||||
| `updated` | string | Last update timestamp |
|
||||
| `noDataState` | string | State when no data is returned |
|
||||
| `execErrState` | string | State on execution error |
|
||||
| `for` | string | Duration the condition must hold before firing |
|
||||
| `keepFiringFor` | string | Duration to keep firing after condition stops |
|
||||
| `missingSeriesEvalsToResolve` | number | Number of missing series evaluations before resolving |
|
||||
| `annotations` | json | Alert annotations |
|
||||
| `labels` | json | Alert labels |
|
||||
| `isPaused` | boolean | Whether the rule is paused |
|
||||
| `folderUID` | string | Parent folder UID |
|
||||
| `ruleGroup` | string | Rule group name |
|
||||
| `orgID` | number | Organization ID |
|
||||
| `provenance` | string | Provisioning source \(empty if API-managed\) |
|
||||
| `notification_settings` | json | Per-rule notification settings \(overrides\) |
|
||||
| `record` | json | Recording rule configuration \(recording rules only\) |
|
||||
|
||||
### `grafana_create_alert_rule`
|
||||
|
||||
@@ -238,22 +263,45 @@ Create a new alert rule
|
||||
| `title` | string | Yes | The title of the alert rule |
|
||||
| `folderUid` | string | Yes | The UID of the folder to create the alert in \(e.g., folder-abc123\) |
|
||||
| `ruleGroup` | string | Yes | The name of the rule group |
|
||||
| `condition` | string | Yes | The refId of the query or expression to use as the alert condition |
|
||||
| `condition` | string | No | The refId of the query or expression to use as the alert condition \(required for alerting rules; omit for recording rules\) |
|
||||
| `data` | string | Yes | JSON array of query/expression data objects |
|
||||
| `forDuration` | string | No | Duration to wait before firing \(e.g., 5m, 1h\) |
|
||||
| `noDataState` | string | No | State when no data is returned \(NoData, Alerting, OK\) |
|
||||
| `execErrState` | string | No | State on execution error \(Alerting, OK\) |
|
||||
| `execErrState` | string | No | State on execution error \(Error, Alerting, OK\) |
|
||||
| `annotations` | string | No | JSON object of annotations |
|
||||
| `labels` | string | No | JSON object of labels |
|
||||
| `uid` | string | No | Optional custom UID for the alert rule |
|
||||
| `isPaused` | boolean | No | Whether the rule is paused on creation |
|
||||
| `keepFiringFor` | string | No | Duration to keep firing after the condition stops \(e.g., 5m\) |
|
||||
| `missingSeriesEvalsToResolve` | number | No | Number of missing series evaluations before resolving |
|
||||
| `notificationSettings` | string | No | JSON object of per-rule notification settings \(overrides\) |
|
||||
| `record` | string | No | JSON object configuring this as a recording rule \(omit for alerting rules\) |
|
||||
| `disableProvenance` | boolean | No | Set X-Disable-Provenance header so the rule remains editable in the Grafana UI |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uid` | string | The UID of the created alert rule |
|
||||
| `id` | number | Alert rule numeric ID |
|
||||
| `uid` | string | Alert rule UID |
|
||||
| `title` | string | Alert rule title |
|
||||
| `condition` | string | RefId of the query used as the alert condition |
|
||||
| `data` | json | Alert rule query/expression data array |
|
||||
| `updated` | string | Last update timestamp |
|
||||
| `noDataState` | string | State when no data is returned |
|
||||
| `execErrState` | string | State on execution error |
|
||||
| `for` | string | Duration the condition must hold before firing |
|
||||
| `keepFiringFor` | string | Duration to keep firing after condition stops |
|
||||
| `missingSeriesEvalsToResolve` | number | Number of missing series evaluations before resolving |
|
||||
| `annotations` | json | Alert annotations |
|
||||
| `labels` | json | Alert labels |
|
||||
| `isPaused` | boolean | Whether the rule is paused |
|
||||
| `folderUID` | string | Parent folder UID |
|
||||
| `ruleGroup` | string | Rule group name |
|
||||
| `orgID` | number | Organization ID |
|
||||
| `provenance` | string | Provisioning source \(empty if API-managed\) |
|
||||
| `notification_settings` | json | Per-rule notification settings \(overrides\) |
|
||||
| `record` | json | Recording rule configuration \(recording rules only\) |
|
||||
|
||||
### `grafana_update_alert_rule`
|
||||
|
||||
@@ -274,18 +322,40 @@ Update an existing alert rule. Fetches the current rule and merges your changes.
|
||||
| `data` | string | No | New JSON array of query/expression data objects |
|
||||
| `forDuration` | string | No | Duration to wait before firing \(e.g., 5m, 1h\) |
|
||||
| `noDataState` | string | No | State when no data is returned \(NoData, Alerting, OK\) |
|
||||
| `execErrState` | string | No | State on execution error \(Alerting, OK\) |
|
||||
| `execErrState` | string | No | State on execution error \(Error, Alerting, OK\) |
|
||||
| `annotations` | string | No | JSON object of annotations |
|
||||
| `labels` | string | No | JSON object of labels |
|
||||
| `isPaused` | boolean | No | Whether the rule is paused |
|
||||
| `keepFiringFor` | string | No | Duration to keep firing after the condition stops \(e.g., 5m\) |
|
||||
| `missingSeriesEvalsToResolve` | number | No | Number of missing series evaluations before resolving |
|
||||
| `notificationSettings` | string | No | JSON object of per-rule notification settings \(overrides\) |
|
||||
| `record` | string | No | JSON object configuring this as a recording rule |
|
||||
| `disableProvenance` | boolean | No | Set X-Disable-Provenance header so the rule remains editable in the Grafana UI |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `uid` | string | The UID of the updated alert rule |
|
||||
| `id` | number | Alert rule numeric ID |
|
||||
| `uid` | string | Alert rule UID |
|
||||
| `title` | string | Alert rule title |
|
||||
| `condition` | string | RefId of the query used as the alert condition |
|
||||
| `data` | json | Alert rule query/expression data array |
|
||||
| `updated` | string | Last update timestamp |
|
||||
| `noDataState` | string | State when no data is returned |
|
||||
| `execErrState` | string | State on execution error |
|
||||
| `for` | string | Duration the condition must hold before firing |
|
||||
| `keepFiringFor` | string | Duration to keep firing after condition stops |
|
||||
| `missingSeriesEvalsToResolve` | number | Number of missing series evaluations before resolving |
|
||||
| `annotations` | json | Alert annotations |
|
||||
| `labels` | json | Alert labels |
|
||||
| `isPaused` | boolean | Whether the rule is paused |
|
||||
| `folderUID` | string | Parent folder UID |
|
||||
| `ruleGroup` | string | Rule group name |
|
||||
| `orgID` | number | Organization ID |
|
||||
| `provenance` | string | Provisioning source \(empty if API-managed\) |
|
||||
| `notification_settings` | json | Per-rule notification settings \(overrides\) |
|
||||
| `record` | json | Recording rule configuration \(recording rules only\) |
|
||||
|
||||
### `grafana_delete_alert_rule`
|
||||
|
||||
@@ -317,6 +387,7 @@ List all alert notification contact points
|
||||
| `apiKey` | string | Yes | Grafana Service Account Token |
|
||||
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `name` | string | No | Filter contact points by exact name match |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -327,6 +398,8 @@ List all alert notification contact points
|
||||
| ↳ `name` | string | Contact point name |
|
||||
| ↳ `type` | string | Notification type \(email, slack, etc.\) |
|
||||
| ↳ `settings` | object | Type-specific settings |
|
||||
| ↳ `disableResolveMessage` | boolean | Whether resolve messages are disabled |
|
||||
| ↳ `provenance` | string | Provisioning source \(empty if API-managed\) |
|
||||
|
||||
### `grafana_create_annotation`
|
||||
|
||||
@@ -341,7 +414,7 @@ Create an annotation on a dashboard or as a global annotation
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `text` | string | Yes | The text content of the annotation |
|
||||
| `tags` | string | No | Comma-separated list of tags |
|
||||
| `dashboardUid` | string | Yes | UID of the dashboard to add the annotation to \(e.g., abc123def\) |
|
||||
| `dashboardUid` | string | No | UID of the dashboard to add the annotation to \(e.g., abc123def\). Omit to create a global organization annotation. |
|
||||
| `panelId` | number | No | ID of the panel to add the annotation to \(e.g., 1, 2\) |
|
||||
| `time` | number | No | Start time in epoch milliseconds \(e.g., 1704067200000, defaults to now\) |
|
||||
| `timeEnd` | number | No | End time in epoch milliseconds for range annotations \(e.g., 1704153600000\) |
|
||||
@@ -366,8 +439,11 @@ Query annotations by time range, dashboard, or tags
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `from` | number | No | Start time in epoch milliseconds \(e.g., 1704067200000\) |
|
||||
| `to` | number | No | End time in epoch milliseconds \(e.g., 1704153600000\) |
|
||||
| `dashboardUid` | string | Yes | Dashboard UID to query annotations from \(e.g., abc123def\) |
|
||||
| `dashboardUid` | string | No | Dashboard UID to query annotations from \(e.g., abc123def\). Omit to query annotations across the organization. |
|
||||
| `dashboardId` | number | No | Legacy numeric dashboard ID filter \(prefer dashboardUid\) |
|
||||
| `panelId` | number | No | Filter by panel ID \(e.g., 1, 2\) |
|
||||
| `alertId` | number | No | Filter by alert ID |
|
||||
| `userId` | number | No | Filter by ID of the user who created the annotation |
|
||||
| `tags` | string | No | Comma-separated list of tags to filter by |
|
||||
| `type` | string | No | Filter by type \(alert or annotation\) |
|
||||
| `limit` | number | No | Maximum number of annotations to return |
|
||||
@@ -378,17 +454,19 @@ Query annotations by time range, dashboard, or tags
|
||||
| --------- | ---- | ----------- |
|
||||
| `annotations` | array | List of annotations |
|
||||
| ↳ `id` | number | Annotation ID |
|
||||
| ↳ `alertId` | number | Associated alert ID \(0 if not alert-driven\) |
|
||||
| ↳ `dashboardId` | number | Dashboard ID |
|
||||
| ↳ `dashboardUID` | string | Dashboard UID |
|
||||
| ↳ `created` | number | Creation timestamp in epoch ms |
|
||||
| ↳ `updated` | number | Last update timestamp in epoch ms |
|
||||
| ↳ `panelId` | number | Panel ID within the dashboard |
|
||||
| ↳ `userId` | number | ID of the user who created the annotation |
|
||||
| ↳ `userName` | string | Username of the user who created the annotation |
|
||||
| ↳ `newState` | string | New alert state \(alert annotations only\) |
|
||||
| ↳ `prevState` | string | Previous alert state \(alert annotations only\) |
|
||||
| ↳ `time` | number | Start time in epoch ms |
|
||||
| ↳ `timeEnd` | number | End time in epoch ms |
|
||||
| ↳ `text` | string | Annotation text |
|
||||
| ↳ `metric` | string | Metric associated with the annotation |
|
||||
| ↳ `tags` | array | Annotation tags |
|
||||
| ↳ `login` | string | Login of the user who created the annotation |
|
||||
| ↳ `email` | string | Email of the user who created the annotation |
|
||||
| ↳ `avatarUrl` | string | Avatar URL of the user |
|
||||
| ↳ `data` | json | Additional annotation data object from Grafana |
|
||||
|
||||
### `grafana_update_annotation`
|
||||
@@ -403,7 +481,7 @@ Update an existing annotation
|
||||
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `annotationId` | number | Yes | The ID of the annotation to update |
|
||||
| `text` | string | Yes | New text content for the annotation |
|
||||
| `text` | string | No | New text content for the annotation \(PATCH supports partial updates\) |
|
||||
| `tags` | string | No | Comma-separated list of new tags |
|
||||
| `time` | number | No | New start time in epoch milliseconds \(e.g., 1704067200000\) |
|
||||
| `timeEnd` | number | No | New end time in epoch milliseconds \(e.g., 1704153600000\) |
|
||||
@@ -453,10 +531,22 @@ List all data sources configured in Grafana
|
||||
| `dataSources` | array | List of data sources |
|
||||
| ↳ `id` | number | Data source ID |
|
||||
| ↳ `uid` | string | Data source UID |
|
||||
| ↳ `orgId` | number | Organization ID |
|
||||
| ↳ `name` | string | Data source name |
|
||||
| ↳ `type` | string | Data source type \(prometheus, mysql, etc.\) |
|
||||
| ↳ `typeLogoUrl` | string | Logo URL for the data source type |
|
||||
| ↳ `access` | string | Access mode \(proxy or direct\) |
|
||||
| ↳ `url` | string | Data source URL |
|
||||
| ↳ `user` | string | Username used to connect |
|
||||
| ↳ `database` | string | Database name \(if applicable\) |
|
||||
| ↳ `basicAuth` | boolean | Whether basic auth is enabled |
|
||||
| ↳ `basicAuthUser` | string | Basic auth username |
|
||||
| ↳ `withCredentials` | boolean | Whether to send credentials with cross-origin requests |
|
||||
| ↳ `isDefault` | boolean | Whether this is the default data source |
|
||||
| ↳ `jsonData` | object | Type-specific JSON configuration |
|
||||
| ↳ `secureJsonFields` | object | Map of secure fields that are set \(values are not returned\) |
|
||||
| ↳ `version` | number | Data source version |
|
||||
| ↳ `readOnly` | boolean | Whether the data source is read-only |
|
||||
|
||||
### `grafana_get_data_source`
|
||||
|
||||
@@ -477,12 +567,22 @@ Get a data source by its ID or UID
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | number | Data source ID |
|
||||
| `uid` | string | Data source UID |
|
||||
| `orgId` | number | Organization ID |
|
||||
| `name` | string | Data source name |
|
||||
| `type` | string | Data source type |
|
||||
| `typeLogoUrl` | string | Logo URL for the data source type |
|
||||
| `access` | string | Access mode \(proxy or direct\) |
|
||||
| `url` | string | Data source connection URL |
|
||||
| `user` | string | Username used to connect |
|
||||
| `database` | string | Database name \(if applicable\) |
|
||||
| `basicAuth` | boolean | Whether basic auth is enabled |
|
||||
| `basicAuthUser` | string | Basic auth username |
|
||||
| `withCredentials` | boolean | Whether to send credentials with cross-origin requests |
|
||||
| `isDefault` | boolean | Whether this is the default data source |
|
||||
| `jsonData` | json | Additional data source configuration |
|
||||
| `secureJsonFields` | object | Map of secure fields that are set \(values are not returned\) |
|
||||
| `version` | number | Data source version |
|
||||
| `readOnly` | boolean | Whether the data source is read-only |
|
||||
|
||||
### `grafana_list_folders`
|
||||
|
||||
@@ -497,6 +597,7 @@ List all folders in Grafana
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `limit` | number | No | Maximum number of folders to return |
|
||||
| `page` | number | No | Page number for pagination |
|
||||
| `parentUid` | string | No | List children of this folder UID \(requires nested folders enabled\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -506,16 +607,18 @@ List all folders in Grafana
|
||||
| ↳ `id` | number | Folder ID |
|
||||
| ↳ `uid` | string | Folder UID |
|
||||
| ↳ `title` | string | Folder title |
|
||||
| ↳ `url` | string | Folder URL path |
|
||||
| ↳ `parentUid` | string | Parent folder UID \(nested folders only\) |
|
||||
| ↳ `parents` | array | Ancestor folder hierarchy \(nested folders only\) |
|
||||
| ↳ `hasAcl` | boolean | Whether the folder has custom ACL permissions |
|
||||
| ↳ `canSave` | boolean | Whether the current user can save the folder |
|
||||
| ↳ `canEdit` | boolean | Whether the current user can edit the folder |
|
||||
| ↳ `canAdmin` | boolean | Whether the current user has admin rights |
|
||||
| ↳ `canDelete` | boolean | Whether the current user can delete the folder |
|
||||
| ↳ `createdBy` | string | Username of who created the folder |
|
||||
| ↳ `created` | string | Timestamp when the folder was created |
|
||||
| ↳ `updatedBy` | string | Username of who last updated the folder |
|
||||
| ↳ `updated` | string | Timestamp when the folder was last updated |
|
||||
| ↳ `version` | number | Version number of the folder |
|
||||
| ↳ `version` | number | Folder version number |
|
||||
|
||||
### `grafana_create_folder`
|
||||
|
||||
@@ -530,6 +633,7 @@ Create a new folder in Grafana
|
||||
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
|
||||
| `title` | string | Yes | The title of the new folder |
|
||||
| `uid` | string | No | Optional UID for the folder \(auto-generated if not provided\) |
|
||||
| `parentUid` | string | No | Parent folder UID for nested folders \(requires nested folders enabled\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -539,11 +643,12 @@ Create a new folder in Grafana
|
||||
| `uid` | string | The UID of the created folder |
|
||||
| `title` | string | The title of the created folder |
|
||||
| `url` | string | The URL path to the folder |
|
||||
| `parentUid` | string | Parent folder UID \(nested folders only\) |
|
||||
| `parents` | array | Ancestor folder hierarchy \(nested folders only\) |
|
||||
| `hasAcl` | boolean | Whether the folder has custom ACL permissions |
|
||||
| `canSave` | boolean | Whether the current user can save the folder |
|
||||
| `canEdit` | boolean | Whether the current user can edit the folder |
|
||||
| `canAdmin` | boolean | Whether the current user has admin rights on the folder |
|
||||
| `canDelete` | boolean | Whether the current user can delete the folder |
|
||||
| `createdBy` | string | Username of who created the folder |
|
||||
| `created` | string | Timestamp when the folder was created |
|
||||
| `updatedBy` | string | Username of who last updated the folder |
|
||||
|
||||
@@ -126,6 +126,33 @@ Return ONLY the search query - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'tag1, tag2 (comma-separated)',
|
||||
condition: { field: 'operation', value: 'grafana_list_dashboards' },
|
||||
},
|
||||
{
|
||||
id: 'folderUIDs',
|
||||
title: 'Folder UIDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'uid1, uid2 (comma-separated)',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_list_dashboards' },
|
||||
},
|
||||
{
|
||||
id: 'dashboardUIDs',
|
||||
title: 'Dashboard UIDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'uid1, uid2 (comma-separated)',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_list_dashboards' },
|
||||
},
|
||||
{
|
||||
id: 'page',
|
||||
title: 'Page',
|
||||
type: 'short-input',
|
||||
placeholder: 'Page number (1-based)',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_list_dashboards', 'grafana_list_folders'],
|
||||
},
|
||||
},
|
||||
|
||||
// Create/Update Dashboard
|
||||
{
|
||||
@@ -156,13 +183,15 @@ Return ONLY the title - no explanations, no quotes, no extra text.`,
|
||||
id: 'folderUid',
|
||||
title: 'Folder UID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional - folder to create dashboard in',
|
||||
placeholder: 'Folder UID (required for alert rules, optional for dashboards)',
|
||||
required: { field: 'operation', value: 'grafana_create_alert_rule' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'grafana_create_dashboard',
|
||||
'grafana_update_dashboard',
|
||||
'grafana_create_alert_rule',
|
||||
'grafana_update_alert_rule',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -229,6 +258,16 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
|
||||
value: ['grafana_create_dashboard', 'grafana_update_dashboard'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'overwrite',
|
||||
title: 'Overwrite on Conflict',
|
||||
type: 'switch',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_dashboard', 'grafana_update_dashboard'],
|
||||
},
|
||||
},
|
||||
|
||||
// Alert Rule operations
|
||||
{
|
||||
@@ -268,16 +307,6 @@ Return ONLY the alert title - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Describe the alert...',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'folderUid',
|
||||
title: 'Folder UID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Folder UID for the alert rule',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ruleGroup',
|
||||
title: 'Rule Group',
|
||||
@@ -380,10 +409,105 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
|
||||
title: 'Error State',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Error', id: 'Error' },
|
||||
{ label: 'Alerting', id: 'Alerting' },
|
||||
{ label: 'OK', id: 'OK' },
|
||||
],
|
||||
value: () => 'Alerting',
|
||||
value: () => 'Error',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'annotations',
|
||||
title: 'Annotations (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON object of alert annotations (e.g., {"summary":"..."})',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'labels',
|
||||
title: 'Labels (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON object of alert labels (e.g., {"severity":"critical"})',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'isPaused',
|
||||
title: 'Paused',
|
||||
type: 'switch',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'keepFiringFor',
|
||||
title: 'Keep Firing For',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., 5m',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'missingSeriesEvalsToResolve',
|
||||
title: 'Missing Series Evals to Resolve',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., 2',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'notificationSettings',
|
||||
title: 'Notification Settings (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON object of per-rule notification overrides',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'record',
|
||||
title: 'Recording Rule (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON object configuring this as a recording rule',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alertRuleUidNew',
|
||||
title: 'Custom Alert Rule UID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional - auto-generated if not provided',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_create_alert_rule' },
|
||||
},
|
||||
{
|
||||
id: 'disableProvenance',
|
||||
title: 'Disable Provenance',
|
||||
type: 'switch',
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
|
||||
@@ -396,7 +520,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
|
||||
title: 'Annotation Text',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter annotation text...',
|
||||
required: true,
|
||||
required: { field: 'operation', value: 'grafana_create_annotation' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_annotation', 'grafana_update_annotation'],
|
||||
@@ -436,8 +560,7 @@ Return ONLY the annotation text - no explanations, no quotes, no extra text.`,
|
||||
id: 'annotationDashboardUid',
|
||||
title: 'Dashboard UID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter dashboard UID',
|
||||
required: true,
|
||||
placeholder: 'Optional - omit for organization-wide annotations',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['grafana_create_annotation', 'grafana_list_annotations'],
|
||||
@@ -453,6 +576,22 @@ Return ONLY the annotation text - no explanations, no quotes, no extra text.`,
|
||||
value: ['grafana_create_annotation', 'grafana_list_annotations'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alertId',
|
||||
title: 'Alert ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by alert ID',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_list_annotations' },
|
||||
},
|
||||
{
|
||||
id: 'userId',
|
||||
title: 'User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by creator user ID',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_list_annotations' },
|
||||
},
|
||||
{
|
||||
id: 'time',
|
||||
title: 'Time (epoch ms)',
|
||||
@@ -583,6 +722,30 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
|
||||
placeholder: 'Optional - auto-generated if not provided',
|
||||
condition: { field: 'operation', value: 'grafana_create_folder' },
|
||||
},
|
||||
{
|
||||
id: 'parentUidNew',
|
||||
title: 'Parent Folder UID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional - for nested folders',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_create_folder' },
|
||||
},
|
||||
{
|
||||
id: 'parentUidList',
|
||||
title: 'Parent Folder UID',
|
||||
type: 'short-input',
|
||||
placeholder: 'List children of this folder UID',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_list_folders' },
|
||||
},
|
||||
{
|
||||
id: 'contactPointName',
|
||||
title: 'Contact Point Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by exact name',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'grafana_list_contact_points' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
@@ -607,22 +770,30 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
|
||||
'grafana_create_folder',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
if (params.alertTitle) params.title = params.alertTitle
|
||||
if (params.folderTitle) params.title = params.folderTitle
|
||||
if (params.folderUidNew) params.uid = params.folderUidNew
|
||||
if (params.annotationTags) params.tags = params.annotationTags
|
||||
if (params.annotationDashboardUid) params.dashboardUid = params.annotationDashboardUid
|
||||
return params.operation
|
||||
},
|
||||
tool: (params) => params.operation,
|
||||
params: (params) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
if (params.alertTitle) result.title = params.alertTitle
|
||||
if (params.folderTitle) result.title = params.folderTitle
|
||||
if (params.folderUidNew) result.uid = params.folderUidNew
|
||||
if (params.alertRuleUidNew) result.uid = params.alertRuleUidNew
|
||||
if (params.parentUidNew) result.parentUid = params.parentUidNew
|
||||
if (params.parentUidList) result.parentUid = params.parentUidList
|
||||
if (params.contactPointName) result.name = params.contactPointName
|
||||
if (params.annotationTags) result.tags = params.annotationTags
|
||||
if (params.annotationDashboardUid) result.dashboardUid = params.annotationDashboardUid
|
||||
if (params.panelId) result.panelId = Number(params.panelId)
|
||||
if (params.annotationId) result.annotationId = Number(params.annotationId)
|
||||
if (params.alertId) result.alertId = Number(params.alertId)
|
||||
if (params.userId) result.userId = Number(params.userId)
|
||||
if (params.time) result.time = Number(params.time)
|
||||
if (params.timeEnd) result.timeEnd = Number(params.timeEnd)
|
||||
if (params.from) result.from = Number(params.from)
|
||||
if (params.to) result.to = Number(params.to)
|
||||
if (params.page) result.page = Number(params.page)
|
||||
if (params.missingSeriesEvalsToResolve) {
|
||||
result.missingSeriesEvalsToResolve = Number(params.missingSeriesEvalsToResolve)
|
||||
}
|
||||
return result
|
||||
},
|
||||
},
|
||||
@@ -641,8 +812,15 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
tag: { type: 'string', description: 'Filter by tag' },
|
||||
folderUIDs: {
|
||||
type: 'string',
|
||||
description: 'Filter dashboards by folder UIDs (comma-separated)',
|
||||
},
|
||||
dashboardUIDs: { type: 'string', description: 'Filter by dashboard UIDs (comma-separated)' },
|
||||
page: { type: 'number', description: 'Page number for pagination' },
|
||||
// Alert inputs
|
||||
alertRuleUid: { type: 'string', description: 'Alert rule UID' },
|
||||
alertRuleUidNew: { type: 'string', description: 'Custom UID for newly created alert rule' },
|
||||
alertTitle: { type: 'string', description: 'Alert rule title' },
|
||||
ruleGroup: { type: 'string', description: 'Rule group name' },
|
||||
condition: { type: 'string', description: 'Alert condition refId' },
|
||||
@@ -650,14 +828,46 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
|
||||
forDuration: { type: 'string', description: 'Duration before firing' },
|
||||
noDataState: { type: 'string', description: 'State on no data' },
|
||||
execErrState: { type: 'string', description: 'State on error' },
|
||||
isPaused: { type: 'boolean', description: 'Whether the alert rule is paused' },
|
||||
keepFiringFor: {
|
||||
type: 'string',
|
||||
description: 'Duration to keep firing after the condition stops',
|
||||
},
|
||||
missingSeriesEvalsToResolve: {
|
||||
type: 'number',
|
||||
description: 'Missing series evaluations before resolving',
|
||||
},
|
||||
notificationSettings: {
|
||||
type: 'string',
|
||||
description: 'JSON of per-rule notification settings',
|
||||
},
|
||||
record: { type: 'string', description: 'JSON of recording rule configuration' },
|
||||
disableProvenance: {
|
||||
type: 'boolean',
|
||||
description: 'Disable provenance tracking so the rule remains UI-editable',
|
||||
},
|
||||
annotations: { type: 'string', description: 'JSON of alert annotations' },
|
||||
labels: { type: 'string', description: 'JSON of alert labels' },
|
||||
overwrite: { type: 'boolean', description: 'Overwrite existing dashboard on version conflict' },
|
||||
// Annotation inputs
|
||||
text: { type: 'string', description: 'Annotation text' },
|
||||
annotationId: { type: 'number', description: 'Annotation ID' },
|
||||
annotationTags: { type: 'string', description: 'Annotation tags (comma-separated)' },
|
||||
annotationDashboardUid: { type: 'string', description: 'Annotation dashboard UID' },
|
||||
panelId: { type: 'number', description: 'Panel ID' },
|
||||
time: { type: 'number', description: 'Start time (epoch ms)' },
|
||||
timeEnd: { type: 'number', description: 'End time (epoch ms)' },
|
||||
from: { type: 'number', description: 'Filter from time' },
|
||||
to: { type: 'number', description: 'Filter to time' },
|
||||
alertId: { type: 'number', description: 'Filter annotations by alert ID' },
|
||||
userId: { type: 'number', description: 'Filter annotations by creator user ID' },
|
||||
// Folder inputs
|
||||
folderTitle: { type: 'string', description: 'Folder title for newly created folder' },
|
||||
folderUidNew: { type: 'string', description: 'Custom UID for newly created folder' },
|
||||
parentUidList: { type: 'string', description: 'Parent folder UID to list children of' },
|
||||
parentUidNew: { type: 'string', description: 'Parent folder UID for newly created folder' },
|
||||
// Contact point inputs
|
||||
contactPointName: { type: 'string', description: 'Filter contact points by name' },
|
||||
// Data source inputs
|
||||
dataSourceId: { type: 'string', description: 'Data source ID or UID' },
|
||||
},
|
||||
@@ -675,6 +885,26 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
|
||||
// Alert outputs
|
||||
rules: { type: 'json', description: 'Alert rules list' },
|
||||
contactPoints: { type: 'json', description: 'Contact points list' },
|
||||
condition: { type: 'string', description: 'Alert condition refId' },
|
||||
for: { type: 'string', description: 'Duration the condition must hold before firing' },
|
||||
keepFiringFor: {
|
||||
type: 'string',
|
||||
description: 'Duration to keep firing after the condition stops',
|
||||
},
|
||||
missingSeriesEvalsToResolve: {
|
||||
type: 'number',
|
||||
description: 'Missing series evaluations before resolving',
|
||||
},
|
||||
isPaused: { type: 'boolean', description: 'Whether the alert 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' },
|
||||
noDataState: { type: 'string', description: 'State on no data' },
|
||||
execErrState: { type: 'string', description: 'State on execution error' },
|
||||
notification_settings: { type: 'json', description: 'Per-rule notification settings' },
|
||||
record: { type: 'json', description: 'Recording rule configuration' },
|
||||
updated: { type: 'string', description: 'Last update timestamp' },
|
||||
// Annotation outputs
|
||||
annotations: { type: 'json', description: 'Annotations list' },
|
||||
id: { type: 'number', description: 'Annotation ID' },
|
||||
|
||||
@@ -2,6 +2,8 @@ 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<
|
||||
@@ -52,9 +54,10 @@ export const createAlertRuleTool: ToolConfig<
|
||||
},
|
||||
condition: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The refId of the query or expression to use as the alert condition',
|
||||
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',
|
||||
@@ -78,7 +81,7 @@ export const createAlertRuleTool: ToolConfig<
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'State on execution error (Alerting, OK)',
|
||||
description: 'State on execution error (Error, Alerting, OK)',
|
||||
},
|
||||
annotations: {
|
||||
type: 'string',
|
||||
@@ -92,6 +95,48 @@ export const createAlertRuleTool: ToolConfig<
|
||||
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: {
|
||||
@@ -105,32 +150,43 @@ export const createAlertRuleTool: ToolConfig<
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
if (params.disableProvenance) {
|
||||
headers['X-Disable-Provenance'] = 'true'
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
let dataArray: any[] = []
|
||||
let dataArray: unknown[] = []
|
||||
try {
|
||||
dataArray = JSON.parse(params.data)
|
||||
} catch {
|
||||
throw new Error('Invalid JSON for data parameter')
|
||||
}
|
||||
|
||||
const body: Record<string, any> = {
|
||||
const body: Record<string, unknown> = {
|
||||
title: params.title,
|
||||
folderUID: params.folderUid,
|
||||
ruleGroup: params.ruleGroup,
|
||||
condition: params.condition,
|
||||
data: dataArray,
|
||||
for: params.forDuration || '5m',
|
||||
noDataState: params.noDataState || 'NoData',
|
||||
execErrState: params.execErrState || 'Alerting',
|
||||
}
|
||||
if (params.organizationId) body.orgID = Number(params.organizationId)
|
||||
|
||||
if (params.condition) body.condition = params.condition
|
||||
if (params.uid) body.uid = params.uid
|
||||
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 {
|
||||
body.annotations = {}
|
||||
throw new Error('Invalid JSON for annotations parameter')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +194,23 @@ export const createAlertRuleTool: ToolConfig<
|
||||
try {
|
||||
body.labels = JSON.parse(params.labels)
|
||||
} catch {
|
||||
body.labels = {}
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,47 +220,8 @@ export const createAlertRuleTool: ToolConfig<
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
uid: data.uid,
|
||||
title: data.title,
|
||||
condition: data.condition,
|
||||
data: data.data,
|
||||
updated: data.updated,
|
||||
noDataState: data.noDataState,
|
||||
execErrState: data.execErrState,
|
||||
for: data.for,
|
||||
annotations: data.annotations || {},
|
||||
labels: data.labels || {},
|
||||
isPaused: data.isPaused || false,
|
||||
folderUID: data.folderUID,
|
||||
ruleGroup: data.ruleGroup,
|
||||
orgId: data.orgId,
|
||||
namespace_uid: data.namespace_uid,
|
||||
namespace_id: data.namespace_id,
|
||||
provenance: data.provenance || '',
|
||||
},
|
||||
}
|
||||
return { success: true, output: mapAlertRule(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
uid: {
|
||||
type: 'string',
|
||||
description: 'The UID of the created alert rule',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Alert rule title',
|
||||
},
|
||||
folderUID: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID',
|
||||
},
|
||||
ruleGroup: {
|
||||
type: 'string',
|
||||
description: 'Rule group name',
|
||||
},
|
||||
},
|
||||
outputs: ALERT_RULE_OUTPUT_FIELDS,
|
||||
}
|
||||
|
||||
@@ -46,9 +46,10 @@ export const createAnnotationTool: ToolConfig<
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'UID of the dashboard to add the annotation to (e.g., abc123def)',
|
||||
description:
|
||||
'UID of the dashboard to add the annotation to (e.g., abc123def). Omit to create a global organization annotation.',
|
||||
},
|
||||
panelId: {
|
||||
type: 'number',
|
||||
@@ -84,11 +85,15 @@ export const createAnnotationTool: ToolConfig<
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
const body: Record<string, unknown> = {
|
||||
text: params.text,
|
||||
time: params.time || Date.now(),
|
||||
}
|
||||
|
||||
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(',')
|
||||
@@ -96,18 +101,6 @@ export const createAnnotationTool: ToolConfig<
|
||||
.filter((t) => t)
|
||||
}
|
||||
|
||||
if (params.dashboardUid) {
|
||||
body.dashboardUID = params.dashboardUid
|
||||
}
|
||||
|
||||
if (params.panelId) {
|
||||
body.panelId = params.panelId
|
||||
}
|
||||
|
||||
if (params.timeEnd) {
|
||||
body.timeEnd = params.timeEnd
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
@@ -39,6 +39,12 @@ export const createFolderTool: ToolConfig<GrafanaCreateFolderParams, GrafanaCrea
|
||||
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: {
|
||||
@@ -55,13 +61,12 @@ export const createFolderTool: ToolConfig<GrafanaCreateFolderParams, GrafanaCrea
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
const body: Record<string, unknown> = {
|
||||
title: params.title,
|
||||
}
|
||||
|
||||
if (params.uid) {
|
||||
body.uid = params.uid
|
||||
}
|
||||
if (params.uid) body.uid = params.uid
|
||||
if (params.parentUid) body.parentUid = params.parentUid
|
||||
|
||||
return body
|
||||
},
|
||||
@@ -73,80 +78,80 @@ export const createFolderTool: ToolConfig<GrafanaCreateFolderParams, GrafanaCrea
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
uid: data.uid,
|
||||
title: data.title,
|
||||
url: data.url,
|
||||
hasAcl: data.hasAcl || false,
|
||||
canSave: data.canSave || false,
|
||||
canEdit: data.canEdit || false,
|
||||
canAdmin: data.canAdmin || false,
|
||||
canDelete: data.canDelete || false,
|
||||
createdBy: data.createdBy || '',
|
||||
created: data.created || '',
|
||||
updatedBy: data.updatedBy || '',
|
||||
updated: data.updated || '',
|
||||
version: data.version || 0,
|
||||
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: {
|
||||
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: 'The UID of the created folder',
|
||||
description: 'Parent folder UID (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'The title of the created folder',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'The URL path to the folder',
|
||||
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',
|
||||
},
|
||||
canDelete: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can delete 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',
|
||||
},
|
||||
version: { type: 'number', description: 'Version number of the folder', optional: true },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { GrafanaGetAlertRuleParams, GrafanaGetAlertRuleResponse } from '@/tools/grafana/types'
|
||||
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> =
|
||||
@@ -53,71 +58,8 @@ export const getAlertRuleTool: ToolConfig<GrafanaGetAlertRuleParams, GrafanaGetA
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
uid: data.uid,
|
||||
title: data.title,
|
||||
condition: data.condition,
|
||||
data: data.data,
|
||||
updated: data.updated,
|
||||
noDataState: data.noDataState,
|
||||
execErrState: data.execErrState,
|
||||
for: data.for,
|
||||
annotations: data.annotations || {},
|
||||
labels: data.labels || {},
|
||||
isPaused: data.isPaused || false,
|
||||
folderUID: data.folderUID,
|
||||
ruleGroup: data.ruleGroup,
|
||||
orgId: data.orgId,
|
||||
namespace_uid: data.namespace_uid,
|
||||
namespace_id: data.namespace_id,
|
||||
provenance: data.provenance || '',
|
||||
},
|
||||
}
|
||||
return { success: true, output: mapAlertRule(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
uid: {
|
||||
type: 'string',
|
||||
description: 'Alert rule UID',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Alert rule title',
|
||||
},
|
||||
condition: {
|
||||
type: 'string',
|
||||
description: 'Alert condition',
|
||||
},
|
||||
data: {
|
||||
type: 'json',
|
||||
description: 'Alert rule query data',
|
||||
},
|
||||
folderUID: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID',
|
||||
},
|
||||
ruleGroup: {
|
||||
type: 'string',
|
||||
description: 'Rule group name',
|
||||
},
|
||||
noDataState: {
|
||||
type: 'string',
|
||||
description: 'State when no data is returned',
|
||||
},
|
||||
execErrState: {
|
||||
type: 'string',
|
||||
description: 'State on execution error',
|
||||
},
|
||||
annotations: {
|
||||
type: 'json',
|
||||
description: 'Alert annotations',
|
||||
},
|
||||
labels: {
|
||||
type: 'json',
|
||||
description: 'Alert labels',
|
||||
},
|
||||
},
|
||||
outputs: ALERT_RULE_OUTPUT_FIELDS,
|
||||
}
|
||||
|
||||
@@ -43,12 +43,14 @@ export const getDataSourceTool: ToolConfig<
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.baseUrl.replace(/\/$/, '')
|
||||
// Check if it looks like a UID (contains non-numeric characters) or ID
|
||||
const isUid = /[^0-9]/.test(params.dataSourceId)
|
||||
if (isUid) {
|
||||
return `${baseUrl}/api/datasources/uid/${params.dataSourceId}`
|
||||
const id = params.dataSourceId.trim()
|
||||
// Numeric DB id route only matches purely-numeric ids up to int64 length;
|
||||
// anything else is treated as a UID (Grafana UIDs are short slug strings).
|
||||
const isNumericId = /^\d+$/.test(id) && id.length <= 18
|
||||
if (isNumericId) {
|
||||
return `${baseUrl}/api/datasources/${id}`
|
||||
}
|
||||
return `${baseUrl}/api/datasources/${params.dataSourceId}`
|
||||
return `${baseUrl}/api/datasources/uid/${id}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
@@ -69,57 +71,54 @@ export const getDataSourceTool: ToolConfig<
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
uid: data.uid,
|
||||
orgId: data.orgId,
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
typeName: data.typeName,
|
||||
typeLogoUrl: data.typeLogoUrl,
|
||||
access: data.access,
|
||||
url: data.url,
|
||||
user: data.user,
|
||||
database: data.database,
|
||||
basicAuth: data.basicAuth || false,
|
||||
isDefault: data.isDefault || false,
|
||||
jsonData: data.jsonData || {},
|
||||
readOnly: data.readOnly || false,
|
||||
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',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Data source name',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Data source type',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'Data source connection URL',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
description: 'Database name (if applicable)',
|
||||
},
|
||||
isDefault: {
|
||||
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 this is the default data source',
|
||||
description: 'Whether to send credentials with cross-origin requests',
|
||||
optional: true,
|
||||
},
|
||||
jsonData: {
|
||||
type: 'json',
|
||||
description: 'Additional data source configuration',
|
||||
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' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type {
|
||||
GrafanaListAlertRulesParams,
|
||||
GrafanaListAlertRulesResponse,
|
||||
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<
|
||||
@@ -56,25 +58,7 @@ export const listAlertRulesTool: ToolConfig<
|
||||
success: true,
|
||||
output: {
|
||||
rules: Array.isArray(data)
|
||||
? data.map((rule: any) => ({
|
||||
uid: rule.uid,
|
||||
title: rule.title,
|
||||
condition: rule.condition,
|
||||
data: rule.data,
|
||||
updated: rule.updated,
|
||||
noDataState: rule.noDataState,
|
||||
execErrState: rule.execErrState,
|
||||
for: rule.for,
|
||||
annotations: rule.annotations || {},
|
||||
labels: rule.labels || {},
|
||||
isPaused: rule.isPaused || false,
|
||||
folderUID: rule.folderUID,
|
||||
ruleGroup: rule.ruleGroup,
|
||||
orgId: rule.orgId,
|
||||
namespace_uid: rule.namespace_uid,
|
||||
namespace_id: rule.namespace_id,
|
||||
provenance: rule.provenance || '',
|
||||
}))
|
||||
? data.map((rule: Record<string, unknown>) => mapAlertRule(rule))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
@@ -86,15 +70,7 @@ export const listAlertRulesTool: ToolConfig<
|
||||
description: 'List of alert rules',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uid: { type: 'string', description: 'Alert rule UID' },
|
||||
title: { type: 'string', description: 'Alert rule title' },
|
||||
condition: { type: 'string', description: 'Alert condition' },
|
||||
folderUID: { type: 'string', description: 'Parent folder UID' },
|
||||
ruleGroup: { type: 'string', description: 'Rule group name' },
|
||||
noDataState: { type: 'string', description: 'State when no data is returned' },
|
||||
execErrState: { type: 'string', description: 'State on execution error' },
|
||||
},
|
||||
properties: ALERT_RULE_OUTPUT_FIELDS,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,9 +46,16 @@ export const listAnnotationsTool: ToolConfig<
|
||||
},
|
||||
dashboardUid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Dashboard UID to query annotations from (e.g., abc123def)',
|
||||
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',
|
||||
@@ -56,6 +63,18 @@ export const listAnnotationsTool: ToolConfig<
|
||||
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,
|
||||
@@ -84,7 +103,10 @@ export const listAnnotationsTool: ToolConfig<
|
||||
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()))
|
||||
}
|
||||
@@ -109,27 +131,27 @@ export const listAnnotationsTool: ToolConfig<
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Handle potential nested array structure
|
||||
const rawAnnotations = Array.isArray(data) ? data.flat() : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
annotations: rawAnnotations.map((a: any) => ({
|
||||
id: a.id,
|
||||
dashboardId: a.dashboardId,
|
||||
dashboardUID: a.dashboardUID,
|
||||
created: a.created,
|
||||
updated: a.updated,
|
||||
time: a.time,
|
||||
timeEnd: a.timeEnd,
|
||||
text: a.text,
|
||||
tags: a.tags || [],
|
||||
login: a.login,
|
||||
email: a.email,
|
||||
avatarUrl: a.avatarUrl,
|
||||
data: a.data || {},
|
||||
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>) ?? {},
|
||||
})),
|
||||
},
|
||||
}
|
||||
@@ -143,21 +165,36 @@ export const listAnnotationsTool: ToolConfig<
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Annotation ID' },
|
||||
dashboardId: { type: 'number', description: 'Dashboard ID' },
|
||||
dashboardUID: { type: 'string', description: 'Dashboard UID' },
|
||||
created: { type: 'number', description: 'Creation timestamp in epoch ms' },
|
||||
updated: { type: 'number', description: 'Last update timestamp in epoch ms' },
|
||||
time: { type: 'number', description: 'Start time in epoch ms' },
|
||||
timeEnd: { type: 'number', description: 'End time in epoch ms' },
|
||||
text: { type: 'string', description: 'Annotation text' },
|
||||
tags: { type: 'array', items: { type: 'string' }, description: 'Annotation tags' },
|
||||
login: { type: 'string', description: 'Login of the user who created the annotation' },
|
||||
email: { type: 'string', description: 'Email of the user who created the annotation' },
|
||||
avatarUrl: { type: 'string', description: 'Avatar URL of the user' },
|
||||
data: {
|
||||
type: 'json',
|
||||
description: 'Additional annotation data object from Grafana',
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,10 +32,22 @@ export const listContactPointsTool: ToolConfig<
|
||||
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) => `${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/contact-points`,
|
||||
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> = {
|
||||
@@ -56,13 +68,13 @@ export const listContactPointsTool: ToolConfig<
|
||||
success: true,
|
||||
output: {
|
||||
contactPoints: Array.isArray(data)
|
||||
? data.map((cp: any) => ({
|
||||
uid: cp.uid,
|
||||
name: cp.name,
|
||||
type: cp.type,
|
||||
settings: cp.settings || {},
|
||||
disableResolveMessage: cp.disableResolveMessage || false,
|
||||
provenance: cp.provenance || '',
|
||||
? 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) ?? '',
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
@@ -80,6 +92,14 @@ export const listContactPointsTool: ToolConfig<
|
||||
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)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -44,11 +44,17 @@ export const listDashboardsTool: ToolConfig<
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by tag (comma-separated for multiple tags)',
|
||||
},
|
||||
folderIds: {
|
||||
folderUIDs: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by folder IDs (comma-separated, e.g., 1,2,3)',
|
||||
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',
|
||||
@@ -60,7 +66,13 @@ export const listDashboardsTool: ToolConfig<
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Maximum number of dashboards to return',
|
||||
description: 'Maximum number of dashboards to return (default 1000)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Page number for pagination (1-based)',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -74,11 +86,17 @@ export const listDashboardsTool: ToolConfig<
|
||||
if (params.tag) {
|
||||
params.tag.split(',').forEach((t) => searchParams.append('tag', t.trim()))
|
||||
}
|
||||
if (params.folderIds) {
|
||||
params.folderIds.split(',').forEach((id) => searchParams.append('folderIds', id.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()}`
|
||||
},
|
||||
@@ -102,21 +120,19 @@ export const listDashboardsTool: ToolConfig<
|
||||
success: true,
|
||||
output: {
|
||||
dashboards: Array.isArray(data)
|
||||
? data.map((d: any) => ({
|
||||
id: d.id,
|
||||
uid: d.uid,
|
||||
title: d.title,
|
||||
uri: d.uri,
|
||||
url: d.url,
|
||||
slug: d.slug,
|
||||
type: d.type,
|
||||
tags: d.tags || [],
|
||||
isStarred: d.isStarred || false,
|
||||
folderId: d.folderId,
|
||||
folderUid: d.folderUid,
|
||||
folderTitle: d.folderTitle,
|
||||
folderUrl: d.folderUrl,
|
||||
sortMeta: d.sortMeta,
|
||||
? 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,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
|
||||
@@ -56,22 +56,25 @@ export const listDataSourcesTool: ToolConfig<
|
||||
success: true,
|
||||
output: {
|
||||
dataSources: Array.isArray(data)
|
||||
? data.map((ds: any) => ({
|
||||
id: ds.id,
|
||||
uid: ds.uid,
|
||||
orgId: ds.orgId,
|
||||
name: ds.name,
|
||||
type: ds.type,
|
||||
typeName: ds.typeName,
|
||||
typeLogoUrl: ds.typeLogoUrl,
|
||||
access: ds.access,
|
||||
url: ds.url,
|
||||
user: ds.user,
|
||||
database: ds.database,
|
||||
basicAuth: ds.basicAuth || false,
|
||||
isDefault: ds.isDefault || false,
|
||||
jsonData: ds.jsonData || {},
|
||||
readOnly: ds.readOnly || false,
|
||||
? 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,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
@@ -87,10 +90,34 @@ export const listDataSourcesTool: ToolConfig<
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -38,6 +38,12 @@ export const listFoldersTool: ToolConfig<GrafanaListFoldersParams, GrafanaListFo
|
||||
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: {
|
||||
@@ -47,6 +53,7 @@ export const listFoldersTool: ToolConfig<GrafanaListFoldersParams, GrafanaListFo
|
||||
|
||||
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}` : ''}`
|
||||
@@ -71,20 +78,22 @@ export const listFoldersTool: ToolConfig<GrafanaListFoldersParams, GrafanaListFo
|
||||
success: true,
|
||||
output: {
|
||||
folders: Array.isArray(data)
|
||||
? data.map((f: any) => ({
|
||||
id: f.id,
|
||||
uid: f.uid,
|
||||
title: f.title,
|
||||
hasAcl: f.hasAcl || false,
|
||||
canSave: f.canSave || false,
|
||||
canEdit: f.canEdit || false,
|
||||
canAdmin: f.canAdmin || false,
|
||||
canDelete: f.canDelete || false,
|
||||
createdBy: f.createdBy || '',
|
||||
created: f.created || '',
|
||||
updatedBy: f.updatedBy || '',
|
||||
updated: f.updated || '',
|
||||
version: f.version || 0,
|
||||
? 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,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
@@ -101,19 +110,58 @@ export const listFoldersTool: ToolConfig<GrafanaListFoldersParams, GrafanaListFo
|
||||
id: { type: 'number', description: 'Folder ID' },
|
||||
uid: { type: 'string', description: 'Folder UID' },
|
||||
title: { type: 'string', description: 'Folder title' },
|
||||
hasAcl: { type: 'boolean', description: 'Whether the folder has custom ACL permissions' },
|
||||
canSave: { type: 'boolean', description: 'Whether the current user can save the folder' },
|
||||
canEdit: { type: 'boolean', description: 'Whether the current user can edit the folder' },
|
||||
canAdmin: { type: 'boolean', description: 'Whether the current user has admin rights' },
|
||||
canDelete: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the current user can delete the folder',
|
||||
url: { type: 'string', description: 'Folder URL path', optional: true },
|
||||
parentUid: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID (nested folders only)',
|
||||
optional: true,
|
||||
},
|
||||
createdBy: { type: 'string', description: 'Username of who created the folder' },
|
||||
created: { type: 'string', description: 'Timestamp when the folder was created' },
|
||||
updatedBy: { type: 'string', description: 'Username of who last updated the folder' },
|
||||
updated: { type: 'string', description: 'Timestamp when the folder was last updated' },
|
||||
version: { type: 'number', description: 'Version number of the folder' },
|
||||
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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+136
-58
@@ -1,5 +1,47 @@
|
||||
// Common types for Grafana API tools
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
// Common parameters for all Grafana tools
|
||||
interface GrafanaBaseParams {
|
||||
@@ -69,9 +111,9 @@ interface GrafanaDashboard {
|
||||
schemaVersion: number
|
||||
version: number
|
||||
refresh: string
|
||||
panels: any[]
|
||||
templating: any
|
||||
annotations: any
|
||||
panels: Record<string, unknown>[]
|
||||
templating: Record<string, unknown>
|
||||
annotations: Record<string, unknown>
|
||||
time: {
|
||||
from: string
|
||||
to: string
|
||||
@@ -88,26 +130,26 @@ export interface GrafanaGetDashboardResponse extends ToolResponse {
|
||||
export interface GrafanaListDashboardsParams extends GrafanaBaseParams {
|
||||
query?: string
|
||||
tag?: string
|
||||
folderIds?: string
|
||||
folderUIDs?: string
|
||||
dashboardUIDs?: string
|
||||
starred?: boolean
|
||||
limit?: number
|
||||
page?: number
|
||||
}
|
||||
|
||||
interface GrafanaDashboardSearchResult {
|
||||
id: number
|
||||
uid: string
|
||||
title: string
|
||||
uri: string
|
||||
url: string
|
||||
slug: string
|
||||
type: string
|
||||
id: number | null
|
||||
uid: string | null
|
||||
title: string | null
|
||||
uri: string | null
|
||||
url: string | null
|
||||
type: string | null
|
||||
tags: string[]
|
||||
isStarred: boolean
|
||||
folderId: number
|
||||
folderUid: string
|
||||
folderTitle: string
|
||||
folderUrl: string
|
||||
sortMeta: number
|
||||
folderId: number | null
|
||||
folderUid: string | null
|
||||
folderTitle: string | null
|
||||
folderUrl: string | null
|
||||
}
|
||||
|
||||
export interface GrafanaListDashboardsResponse extends ToolResponse {
|
||||
@@ -177,23 +219,26 @@ export interface GrafanaDeleteDashboardResponse extends ToolResponse {
|
||||
export interface GrafanaListAlertRulesParams extends GrafanaBaseParams {}
|
||||
|
||||
interface GrafanaAlertRule {
|
||||
uid: string
|
||||
title: string
|
||||
condition: string
|
||||
data: any[]
|
||||
updated: string
|
||||
noDataState: string
|
||||
execErrState: string
|
||||
for: string
|
||||
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
|
||||
ruleGroup: string
|
||||
orgId: number
|
||||
namespace_uid: string
|
||||
namespace_id: number
|
||||
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 {
|
||||
@@ -214,13 +259,20 @@ export interface GrafanaCreateAlertRuleParams extends GrafanaBaseParams {
|
||||
title: string
|
||||
folderUid: string
|
||||
ruleGroup: string
|
||||
condition: string
|
||||
condition?: string
|
||||
data: string // JSON string of data array
|
||||
forDuration?: string
|
||||
noDataState?: string
|
||||
execErrState?: string
|
||||
annotations?: string // JSON string
|
||||
labels?: string // JSON string
|
||||
uid?: string
|
||||
isPaused?: boolean
|
||||
keepFiringFor?: string
|
||||
missingSeriesEvalsToResolve?: number
|
||||
notificationSettings?: string // JSON string
|
||||
record?: string // JSON string
|
||||
disableProvenance?: boolean
|
||||
}
|
||||
|
||||
export interface GrafanaCreateAlertRuleResponse extends ToolResponse {
|
||||
@@ -239,6 +291,12 @@ export interface GrafanaUpdateAlertRuleParams extends GrafanaBaseParams {
|
||||
execErrState?: string
|
||||
annotations?: string // JSON string
|
||||
labels?: string // JSON string
|
||||
isPaused?: boolean
|
||||
keepFiringFor?: string
|
||||
missingSeriesEvalsToResolve?: number
|
||||
notificationSettings?: string // JSON string
|
||||
record?: string // JSON string
|
||||
disableProvenance?: boolean
|
||||
}
|
||||
|
||||
interface GrafanaUpdateAlertRuleResponse extends ToolResponse {
|
||||
@@ -266,19 +324,21 @@ export interface GrafanaCreateAnnotationParams extends GrafanaBaseParams {
|
||||
}
|
||||
|
||||
interface GrafanaAnnotation {
|
||||
id: number
|
||||
dashboardId: number
|
||||
dashboardUID: string
|
||||
created: number
|
||||
updated: number
|
||||
time: number
|
||||
timeEnd: number
|
||||
text: string
|
||||
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[]
|
||||
login: string
|
||||
email: string
|
||||
avatarUrl: string
|
||||
data: any
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GrafanaCreateAnnotationResponse extends ToolResponse {
|
||||
@@ -291,8 +351,11 @@ export interface GrafanaCreateAnnotationResponse extends ToolResponse {
|
||||
export interface GrafanaListAnnotationsParams extends GrafanaBaseParams {
|
||||
from?: number
|
||||
to?: number
|
||||
dashboardId?: number
|
||||
dashboardUid?: string
|
||||
panelId?: number
|
||||
alertId?: number
|
||||
userId?: number
|
||||
tags?: string // comma-separated
|
||||
type?: string
|
||||
limit?: number
|
||||
@@ -306,7 +369,7 @@ export interface GrafanaListAnnotationsResponse extends ToolResponse {
|
||||
|
||||
export interface GrafanaUpdateAnnotationParams extends GrafanaBaseParams {
|
||||
annotationId: number
|
||||
text: string
|
||||
text?: string
|
||||
tags?: string // comma-separated
|
||||
time?: number
|
||||
timeEnd?: number
|
||||
@@ -338,15 +401,18 @@ interface GrafanaDataSource {
|
||||
orgId: number
|
||||
name: string
|
||||
type: string
|
||||
typeName: string
|
||||
typeLogoUrl: string
|
||||
access: string
|
||||
url: string
|
||||
user: string
|
||||
database: string
|
||||
basicAuth: boolean
|
||||
basicAuthUser?: string
|
||||
withCredentials?: boolean
|
||||
isDefault: boolean
|
||||
jsonData: any
|
||||
jsonData: Record<string, unknown>
|
||||
secureJsonFields?: Record<string, boolean>
|
||||
version?: number
|
||||
readOnly: boolean
|
||||
}
|
||||
|
||||
@@ -368,22 +434,31 @@ export interface GrafanaGetDataSourceResponse extends ToolResponse {
|
||||
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
|
||||
hasAcl: boolean
|
||||
canSave: boolean
|
||||
canEdit: boolean
|
||||
canAdmin: boolean
|
||||
canDelete: boolean
|
||||
createdBy: string
|
||||
created: string
|
||||
updatedBy: string
|
||||
updated: string
|
||||
version: number
|
||||
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 {
|
||||
@@ -395,6 +470,7 @@ export interface GrafanaListFoldersResponse extends ToolResponse {
|
||||
export interface GrafanaCreateFolderParams extends GrafanaBaseParams {
|
||||
title: string
|
||||
uid?: string
|
||||
parentUid?: string
|
||||
}
|
||||
|
||||
export interface GrafanaCreateFolderResponse extends ToolResponse {
|
||||
@@ -402,13 +478,15 @@ export interface GrafanaCreateFolderResponse extends ToolResponse {
|
||||
}
|
||||
|
||||
// Contact Points types
|
||||
export interface GrafanaListContactPointsParams extends GrafanaBaseParams {}
|
||||
export interface GrafanaListContactPointsParams extends GrafanaBaseParams {
|
||||
name?: string
|
||||
}
|
||||
|
||||
interface GrafanaContactPoint {
|
||||
uid: string
|
||||
name: string
|
||||
type: string
|
||||
settings: Record<string, any>
|
||||
settings: Record<string, unknown>
|
||||
disableResolveMessage: boolean
|
||||
provenance: string
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { GrafanaUpdateAlertRuleParams } from '@/tools/grafana/types'
|
||||
import { ALERT_RULE_OUTPUT_FIELDS, type GrafanaUpdateAlertRuleParams } from '@/tools/grafana/types'
|
||||
import { mapAlertRule } from '@/tools/grafana/utils'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
// Using ToolResponse for intermediate state since this tool fetches existing data first
|
||||
@@ -79,7 +80,7 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'State on execution error (Alerting, OK)',
|
||||
description: 'State on execution error (Error, Alerting, OK)',
|
||||
},
|
||||
annotations: {
|
||||
type: 'string',
|
||||
@@ -93,6 +94,42 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
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: {
|
||||
@@ -136,7 +173,7 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
}
|
||||
|
||||
// Build the updated rule by merging existing data with new params
|
||||
const updatedRule: Record<string, any> = {
|
||||
const updatedRule: Record<string, unknown> = {
|
||||
...existingRule,
|
||||
}
|
||||
|
||||
@@ -148,12 +185,45 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
if (params.forDuration) updatedRule.for = params.forDuration
|
||||
if (params.noDataState) updatedRule.noDataState = params.noDataState
|
||||
if (params.execErrState) updatedRule.execErrState = params.execErrState
|
||||
if (params.isPaused !== undefined) updatedRule.isPaused = params.isPaused
|
||||
if (params.keepFiringFor) updatedRule.keep_firing_for = params.keepFiringFor
|
||||
if (params.missingSeriesEvalsToResolve !== undefined) {
|
||||
updatedRule.missingSeriesEvalsToResolve = params.missingSeriesEvalsToResolve
|
||||
}
|
||||
|
||||
if (params.notificationSettings) {
|
||||
try {
|
||||
updatedRule.notification_settings = JSON.parse(params.notificationSettings)
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: 'Invalid JSON for notificationSettings parameter',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (params.record) {
|
||||
try {
|
||||
updatedRule.record = JSON.parse(params.record)
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: 'Invalid JSON for record parameter',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (params.data) {
|
||||
try {
|
||||
updatedRule.data = JSON.parse(params.data)
|
||||
} catch {
|
||||
// Keep existing data if parse fails
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: 'Invalid JSON for data parameter',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +234,11 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
...JSON.parse(params.annotations),
|
||||
}
|
||||
} catch {
|
||||
// Keep existing annotations if parse fails
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: 'Invalid JSON for annotations parameter',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +249,11 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
...JSON.parse(params.labels),
|
||||
}
|
||||
} catch {
|
||||
// Keep existing labels if parse fails
|
||||
return {
|
||||
success: false,
|
||||
output: {},
|
||||
error: 'Invalid JSON for labels parameter',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +265,9 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
if (params.organizationId) {
|
||||
headers['X-Grafana-Org-Id'] = params.organizationId
|
||||
}
|
||||
if (params.disableProvenance) {
|
||||
headers['X-Disable-Provenance'] = 'true'
|
||||
}
|
||||
|
||||
const updateResponse = await fetch(
|
||||
`${params.baseUrl.replace(/\/$/, '')}/api/v1/provisioning/alert-rules/${params.alertRuleUid}`,
|
||||
@@ -207,47 +288,8 @@ export const updateAlertRuleTool: ToolConfig<GrafanaUpdateAlertRuleParams, ToolR
|
||||
}
|
||||
|
||||
const data = await updateResponse.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
uid: data.uid,
|
||||
title: data.title,
|
||||
condition: data.condition,
|
||||
data: data.data,
|
||||
updated: data.updated,
|
||||
noDataState: data.noDataState,
|
||||
execErrState: data.execErrState,
|
||||
for: data.for,
|
||||
annotations: data.annotations || {},
|
||||
labels: data.labels || {},
|
||||
isPaused: data.isPaused || false,
|
||||
folderUID: data.folderUID,
|
||||
ruleGroup: data.ruleGroup,
|
||||
orgId: data.orgId,
|
||||
namespace_uid: data.namespace_uid,
|
||||
namespace_id: data.namespace_id,
|
||||
provenance: data.provenance || '',
|
||||
},
|
||||
}
|
||||
return { success: true, output: mapAlertRule(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
uid: {
|
||||
type: 'string',
|
||||
description: 'The UID of the updated alert rule',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Alert rule title',
|
||||
},
|
||||
folderUID: {
|
||||
type: 'string',
|
||||
description: 'Parent folder UID',
|
||||
},
|
||||
ruleGroup: {
|
||||
type: 'string',
|
||||
description: 'Rule group name',
|
||||
},
|
||||
},
|
||||
outputs: ALERT_RULE_OUTPUT_FIELDS,
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ export const updateAnnotationTool: ToolConfig<
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New text content for the annotation',
|
||||
description: 'New text content for the annotation (PATCH supports partial updates)',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
@@ -78,9 +78,11 @@ export const updateAnnotationTool: ToolConfig<
|
||||
return headers
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
text: params.text,
|
||||
}
|
||||
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
|
||||
@@ -89,14 +91,6 @@ export const updateAnnotationTool: ToolConfig<
|
||||
.filter((t) => t)
|
||||
}
|
||||
|
||||
if (params.time) {
|
||||
body.time = params.time
|
||||
}
|
||||
|
||||
if (params.timeEnd) {
|
||||
body.timeEnd = params.timeEnd
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
@@ -74,7 +74,8 @@ export const updateDashboardTool: ToolConfig<GrafanaUpdateDashboardParams, ToolR
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Overwrite even if there is a version conflict',
|
||||
description:
|
||||
'Overwrite even if there is a version conflict (defaults to false to surface 412 conflicts)',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
@@ -159,7 +160,7 @@ export const updateDashboardTool: ToolConfig<GrafanaUpdateDashboardParams, ToolR
|
||||
// Build the request body
|
||||
const body: Record<string, any> = {
|
||||
dashboard: updatedDashboard,
|
||||
overwrite: params.overwrite !== false,
|
||||
overwrite: params.overwrite === true,
|
||||
}
|
||||
|
||||
// Use existing folder if not specified
|
||||
|
||||
@@ -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