# Dashboards

`dashboard_write` turns PromQL into a Grafana dashboard document in your
organization's Grafana, owned by the member who asked for it;
`dashboard_read` lists the documents you can show. Both are tools on the
[MCP server](https://docs.nationalcompute.com/api/mcp.md), single phase: no preview, no `confirm_token`. They
exist so an agent shows monitoring in Grafana's own UI, live, instead of
a hand-drawn page. The numbers behind a chart come from
[`metrics_query`](https://docs.nationalcompute.com/api/metrics.md#free-form-queries-metrics_query), which
runs the same expressions.

## Documents

One document per chart. Every `create` writes a new dashboard with its
own uid (`marshall-` followed by 12 hex characters) into a `Marshall`
folder of your organization's Grafana org, tagged `marshall`,
`member:<your username>` and `scratch` or `kept`. Several charts at once
are several panels in one document or several documents. The document
is not editable in Grafana: every change goes through `dashboard_write`.

Ownership is by member. `update`, `keep` and `delete` act only on
documents you created; another member's uid is refused as `404
not-found`, the same answer a missing uid gets. Every document is
visible to the whole organization in Grafana, like every dashboard
there; the tool enumerates only your own.

Every panel reads your organization's own monitoring and nothing else:
the datasource is pinned to your organization's tenant, and PromQL has no
syntax for another one.

## Scratch and kept

A new document is **scratch**: it expires 24 hours after its last use
and is then deleted from Grafana. An `update`, a `dashboard_read get`,
or a console pane showing it counts as use. `keep` names the document,
moves it to the `Saved` folder under `Marshall` and removes the expiry;
the uid does not change, so links stay valid. `delete` removes a
document at once, scratch or kept; there is no undo, but nothing beyond
the document is lost.

| Limit | Value |
|---|---|
| Scratch expiry | 24 hours after last use (currently) |
| Scratch documents per member | 20 |
| Scratch documents per organization | 200 |
| Panels per document | 12 |
| Queries per panel | 8 |
| PromQL per query | 4000 characters |
| Title | 120 characters |

Kept documents do not count against the scratch caps.

## `dashboard_write`

| Argument | Values | Meaning |
|---|---|---|
| `action` | `create`, `update`, `keep`, `delete` | required |
| `dashboard` | a uid of your own | required for `update`, `keep`, `delete` |
| `title` | at most 120 characters | required on `create`; renames on `update` and `keep` |
| `panels` | 1 to 12 panel specs, below | required on `create`; replaces the panels on `update` |
| `range` | `1h`, `6h`, `24h`, `7d` (default `6h`) | the window the dashboard opens on |

`update` takes any of `title`, `panels`, `range` and carries the rest
over from the document; at least one is required. `keep` on a kept
document renames it.

A panel spec is `{title, kind, queries, unit?, thresholds?}`:

| Field | Values | Meaning |
|---|---|---|
| `title` | at most 120 characters | the panel title |
| `kind` | `timeseries`, `stat`, `gauge`, `bargauge`, `table` | lines over the window; one number; a gauge; one bar per series; an instant read as rows |
| `queries` | 1 to 8 of `{expr, legend?}` | PromQL, at most 4000 characters each; `legend` is a Grafana legend template such as `{{instance}}` |
| `unit` | a Grafana unit id (`percent`, `celsius`, `bytes`, `watt`, `short`, …) | optional |
| `thresholds` | up to 4 ascending numbers | optional; colors the panel green below the first, then yellow, orange, red |

The answer carries `dashboard` (the uid), `title`, `folder` (`scratch`
or `saved`), `panels` (`[{id, title, kind}]`, ids `1..n`), `range`,
`version`, `created_at`, `last_used_at`, `expires_at` (`null` once
kept), `url` (the document in your organization's Grafana) and
`member_signed_in`. `member_signed_in` is `false` when you have never
opened Grafana: the first visit to the URL signs you in once, and
charts render after that; `null` means the server could not tell.
`delete` answers `{dashboard, title, deleted: true}`.

## `dashboard_read`

| Argument | Values | Meaning |
|---|---|---|
| `action` | `list`, `get` | required |
| `dashboard` | a uid | required for `get` |

`list` answers `dashboards` (your own documents, the shape above),
`platform` (the platform's tenant dashboards in your organization's
Grafana, each with `uid`, `title`, `tags`, `panels` `[{id, title,
kind}]`, `variables` `[{name, label, hidden, current}]` and `url`),
`caps` (`scratch_used`, `scratch_cap`, `org_scratch_used`,
`org_scratch_cap`), `scratch_ttl_s` and `member_signed_in`. Other
members' documents are not listed.

`get` on your own uid answers the document with its `spec` (the panels
as given) and counts as use; `get` on a platform dashboard's uid answers
its panels and variables.

## Refusals

| Code | When | What to do |
|---|---|---|
| `bad-request` (422) | a missing `title` or `panels`, an unknown `kind` or `action`, an expression over 4000 characters, more than 12 panels or 8 queries; Grafana refused the composed document (its message is in `detail`) | fix the spec |
| `not-found` (404) | a uid that does not exist or is another member's | `dashboard_read list` names yours |
| `dashboard-cap` (409) | the per-member or per-organization scratch cap; `detail` names the least recently used scratch document and `oldest` carries its uid | `keep` or `delete` one, then retry |
| `grafana-unavailable` (503) | Grafana did not answer, or your organization has no Grafana org yet | retry shortly; the org appears with its first monitored cluster |
| `dashboards-unavailable` (503) | the environment has no dashboard store | none; the surface is off there |

The member rate limit and every other rule in [MCP server](https://docs.nationalcompute.com/api/mcp.md#limits-and-semantics) apply.
