Skip to main content
Glama

Costory: Your Finops MCP

create_report

Create a report with shared reportContext and one or more widgets plus destinations. Call get_skill with skillId: "reports" first — see skill for context-first workflow and DIGEST preview iteration. Put shared settings in reportContext (period required when query widgets are present: prefer datePreset; plus metricId, common groupBy, currency, optional conditionsCel / scopeId). Query widgets inherit by default and should only specify overrides — do not repeat from/to, datePreset, groupBy, metricId, or currency when they match reportContext. The legacy context alias is temporarily accepted but deprecated; never send both. Schedule modes: • NOW — create an on-demand report and immediately deliver it to every destination. DANGER ZONE: this sends real messages now. Summarize scope, schedule, and every destination, then ask the user to confirm before calling. • UNSCHEDULED — save an on-demand draft without delivery. • SCHEDULED — create a recurring report (DAILY / WEEKDAYS / WEEKLY / MONTHLY). DANGER ZONE: this authorizes future channel delivery. Summarize the recurring schedule and every destination, then ask the user to confirm before calling. Widget types: DASHBOARD_PDF, GRAPH_SNAPSHOT, TOP_FLOP, TEXT, and DIGEST. A DASHBOARD_PDF widget renders an existing dashboard as a PDF — pass { type: "DASHBOARD_PDF", dashboardId: "<id from search>" } (dashboardId required). TEXT widgets use { type: "TEXT", contentMarkdown } (not dashboard textContent). DIGEST hierarchy: reportContext.groupBy (root, preferred) + ordered additionalGroupBy (deeper levels); also supports thresholds. DIGEST AI (opt-in, slower): display ("tree" default | "table" | "summary" for LLM executive narrative) and enableAiInvestigation (boolean, default false — per-node deep analysis, independent of display). DIGEST aggBy is Month or Week — never Period (Period is TOP_FLOP). Preview and create use the same reportContext + widget shape. For WEEKLY schedules, schedule.weekday is required (0 = Sunday … 6 = Saturday). Returns report URL and nextRunAt; NOW also returns execution URLs and per-destination status. EXAMPLE monthly DIGEST: { visibility: "PRIVATE", schedule: { mode: "UNSCHEDULED" }, reportContext: { datePreset: "LAST_MONTH", groupBy: "cos_environment", metricId: "cost", currency: "USD" }, widgets: [{ type: "DIGEST", queries: [{ type: "cost", name: "a" }], aggBy: "Month", additionalGroupBy: ["cos_sub_account_id", "cos_service_name"], minAbsoluteDiff: 100, minRelativeDiff: 5, topLargestAbsoluteChange: 20, display: "summary", enableAiInvestigation: false }], destinations: [{ destinationType: "SLACK", channelId: "C…" }] } EXAMPLE GRAPH_SNAPSHOT + TOP_FLOP: { visibility: "PRIVATE", schedule: { mode: "SCHEDULED", period: "WEEKLY", weekday: 1, firstRunAt: "2026-08-04T10:00:00.000Z" }, reportContext: { metricId: "cost", currency: "USD", groupBy: "cos_service_name", datePreset: "LAST_WEEK" }, widgets: [{ type: "GRAPH_SNAPSHOT", title: "Cost by service — trailing weeks", queries: [{ type: "cost", name: "a", chartType: "LINE" }], datePreset: "TRAILING_14_WEEKS", aggBy: "Week" }, { type: "TOP_FLOP", title: "Last week movers by service", queries: [{ type: "cost", name: "a" }], aggBy: "Period", topN: 5, flopN: 5 }], destinations: [{ destinationType: "SLACK", channelId: "C…" }] }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameNoOptional; empty or omitted → server generates a name from content.
slugNoOrganization slug. Omit to auto-detect from your account (fails if you belong to multiple orgs).
teamIdNoOptional owning team id. Independent of type/visibility; omit or null for no team.
contextNoDeprecated alias for `reportContext`; supported temporarily for backward compatibility. Do not send both fields.
widgetsYesOne or more report widgets (DIGEST / GRAPH_SNAPSHOT / TOP_FLOP / TEXT / DASHBOARD_PDF). For query-backed widgets prefer sparse cost `queries` and inherit period / groupBy / metricId / currency from `reportContext`. Call get_skill skillId: "reports" for examples.
scheduleYesDelivery mode: NOW (sends immediately), UNSCHEDULED (draft), or SCHEDULED (recurring; WEEKLY requires `weekday`).
visibilityNoPRIVATE (default) or PUBLIC (maps to API `type`). Set `teamId` to share with a team — there is no TEAM visibility value. TEMPLATE is dashboard-only.PRIVATE
descriptionNoOptional longer description (default empty).
destinationsYesDelivery targets: `{ destinationType, channelId }` or `{ destinationType: "EMAIL", email }`. Resolve channels via `list_available_destinations`.
reportContextNoReport-level shared context. Put period (prefer datePreset), metricId, common groupBy, currency, optional conditionsCel / scopeId here — query widgets omit those fields to inherit. Call get_skill skillId: "reports" first.

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed2 schema fields changed
    • changedInput schema / properties / visibility / description
      Previous value: -"PRIVATE (default), PUBLIC, or TEMPLATE (maps to API `type`). Set `teamId` to share with a team — there is no TEAM visibility value."New value: +"PRIVATE (default) or PUBLIC (maps to API `type`). Set `teamId` to share with a team — there is no TEAM visibility value. TEMPLATE is dashboard-only."
    • changedInput schema / properties / visibility / enum
      Previous value: -[
      -  "PRIVATE",
      -  "PUBLIC",
      -  "TEMPLATE"
      -]New value: +[
      +  "PRIVATE",
      +  "PUBLIC"
      +]
  2. Changed1 schema field changed
    • changedInput schema / properties / widgets / items / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "dashboardId": {
      -        "description": "Id of the dashboard to render as a PDF (from `search`/`get`).",
      -        "minLength": 1,
      -        "type": "string"
      -      },
      -      "description": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "title": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "type": {
      -        "const": "DASHBOARD_PDF",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "type",
      -      "dashboardId"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "aggBy": {
      -        "default": "Month",
      -        "description": "Time grain for the series: Hour, Day, Week, Month, or Period (default Month).",
      -        "enum": [
      -          "Hour",
      -          "Day",
      -          "Week",
      -          "Month",
      -          "Period"
      -        ],
      -        "type": "string"
      -      },
      -      "compare": {
      -        "additionalProperties": false,
      -        "description": "Add a comparison period to show cost evolution side-by-side. Omit `from`/`to` to compare against the preceding period automatically; set `chartType` to choose how it renders.",
      -        "properties": {
      -          "chartType": {
      -            "default": "WATERFALL",
      -            "description": "How the comparison renders: WATERFALL (default, contribution to change), TABLE (before/after rows), or KPI_BREAKDOWN (per-group KPI cards). Note KPI_BREAKDOWN and TABLE widgets cannot be exported as PNG via get_widget_png.",
      -            "enum": [
      -              "WATERFALL",
      -              "TABLE",
      -              "KPI_BREAKDOWN"
      -            ],
      -            "type": "string"
      -          },
      -          "enabled": {
      -            "default": true,
      -            "description": "Defaults to true — supplying `compare` at all turns the comparison on. Pass false to keep the block while disabling it (useful when echoing back a payload from `get`).",
      -            "type": "boolean"
      -          },
      -          "from": {
      -            "description": "Comparison period start date. Omit (together with `to`) to compare against the preceding period, derived from the main period.",
      -            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      -            "type": "string"
      -          },
      -          "to": {
      -            "description": "Comparison period end date. Required together with `from`; omit both to auto-derive.",
      -            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      -            "type": "string"
      -          }
      -        },
      -        "type": "object"
      -      },
      -      "datePreset": {
      -        "description": "Widget-level period override (same DatePreset as dashboards, e.g. LAST_WEEK). Omit to inherit `reportContext.datePreset` / startDate/endDate. Use either datePreset OR explicit from/to, not both.",
      -        "enum": [
      -          "TRAILING_90_DAYS",
      -          "TRAILING_30_DAYS",
      -          "TRAILING_45_DAYS",
      -          "TRAILING_7_DAYS",
      -          "TRAILING_3_DAYS",
      -          "TRAILING_14_WEEKS",
      -          "MTD",
      -          "QTD",
      -          "YTD",
      -          "LAST_WEEK",
      -          "LAST_MONTH",
      -          "LAST_6_MONTHS",
      -          "LAST_12_MONTHS",
      -          "LAST_4_YEARS",
      -          "LAST_3_MONTHS",
      -          "LAST_INVOICE_MONTH"
      -        ],
      -        "type": "string"
      -      },
      -      "description": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "from": {
      -        "description": "Widget-level start date override. Omit to inherit report context period.",
      -        "format": "date",
      -        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -        "type": "string"
      -      },
      -      "limit": {
      -        "description": "Max groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.",
      -        "maximum": 1000,
      -        "minimum": 1,
      -        "type": "integer"
      -      },
      -      "queries": {
      -        "description": "Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (prefer short ids like a/b/c); put human labels in `alias`. On report widgets prefer sparse `{ type: \"cost\", name: \"a\", alias?, filterCel? }` and inherit metricId / currency / groupBy / period from `reportContext`. DIGEST: exactly one `{ type: \"cost\", ... }`. Call get_skill skillId: \"reports\" for examples.",
      -        "items": {
      -          "additionalProperties": {},
      -          "propertyNames": {
      -            "type": "string"
      -          },
      -          "type": "object"
      -        },
      -        "type": "array"
      -      },
      -      "scopeId": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Optional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries."
      -      },
      -      "title": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "to": {
      -        "description": "Widget-level end date override (inclusive). Omit to inherit report context period.",
      -        "format": "date",
      -        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -        "type": "string"
      -      },
      -      "type": {
      -        "const": "GRAPH_SNAPSHOT",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "type"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "aggBy": {
      -        "default": "Month",
      -        "description": "Time grain for the series: Hour, Day, Week, Month, or Period (default Month).",
      -        "enum": [
      -          "Hour",
      -          "Day",
      -          "Week",
      -          "Month",
      -          "Period"
      -        ],
      -        "type": "string"
      -      },
      -      "datePreset": {
      -        "description": "Widget-level period override (same DatePreset as dashboards, e.g. LAST_WEEK). Omit to inherit `reportContext.datePreset` / startDate/endDate. Use either datePreset OR explicit from/to, not both.",
      -        "enum": [
      -          "TRAILING_90_DAYS",
      -          "TRAILING_30_DAYS",
      -          "TRAILING_45_DAYS",
      -          "TRAILING_7_DAYS",
      -          "TRAILING_3_DAYS",
      -          "TRAILING_14_WEEKS",
      -          "MTD",
      -          "QTD",
      -          "YTD",
      -          "LAST_WEEK",
      -          "LAST_MONTH",
      -          "LAST_6_MONTHS",
      -          "LAST_12_MONTHS",
      -          "LAST_4_YEARS",
      -          "LAST_3_MONTHS",
      -          "LAST_INVOICE_MONTH"
      -        ],
      -        "type": "string"
      -      },
      -      "description": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "flopN": {
      -        "default": 5,
      -        "maximum": 50,
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "from": {
      -        "description": "Widget-level start date override. Omit to inherit report context period.",
      -        "format": "date",
      -        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -        "type": "string"
      -      },
      -      "limit": {
      -        "description": "Max groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.",
      -        "maximum": 1000,
      -        "minimum": 1,
      -        "type": "integer"
      -      },
      -      "queries": {
      -        "description": "Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (prefer short ids like a/b/c); put human labels in `alias`. On report widgets prefer sparse `{ type: \"cost\", name: \"a\", alias?, filterCel? }` and inherit metricId / currency / groupBy / period from `reportContext`. DIGEST: exactly one `{ type: \"cost\", ... }`. Call get_skill skillId: \"reports\" for examples.",
      -        "items": {
      -          "additionalProperties": {},
      -          "propertyNames": {
      -            "type": "string"
      -          },
      -          "type": "object"
      -        },
      -        "type": "array"
      -      },
      -      "scopeId": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Optional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries."
      -      },
      -      "title": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "to": {
      -        "description": "Widget-level end date override (inclusive). Omit to inherit report context period.",
      -        "format": "date",
      -        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -        "type": "string"
      -      },
      -      "topN": {
      -        "default": 5,
      -        "maximum": 50,
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "type": {
      -        "const": "TOP_FLOP",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "type"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "contentMarkdown": {
      -        "maxLength": 10000,
      -        "minLength": 1,
      -        "type": "string"
      -      },
      -      "description": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "title": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "type": {
      -        "const": "TEXT",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "type",
      -      "contentMarkdown"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "additionalGroupBy": {
      -        "default": [],
      -        "description": "Deeper DIGEST hierarchy levels AFTER the root groupBy, in order. Root axis is `reportContext.groupBy` (preferred) or `queries[0].groupBy` when overriding. Example: reportContext.groupBy=\"cos_environment\" + additionalGroupBy=[\"cos_sub_account_id\",\"cos_service_name\"] → tree path environment → project → service.",
      -        "items": {
      -          "minLength": 1,
      -          "type": "string"
      -        },
      -        "type": "array"
      -      },
      -      "aggBy": {
      -        "default": "Month",
      -        "description": "DIGEST time grain: Week or Month (default Month). Never Period (use TOP_FLOP for whole-period movers).",
      -        "enum": [
      -          "Week",
      -          "Month"
      -        ],
      -        "type": "string"
      -      },
      -      "compare": {
      -        "description": "Required only with explicit from/to when not using datePreset or reportContext. Prefer datePreset / reportContext.",
      -        "properties": {
      -          "from": {
      -            "description": "Comparison period start date.",
      -            "format": "date",
      -            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -            "type": "string"
      -          },
      -          "to": {
      -            "description": "Comparison period end date.",
      -            "format": "date",
      -            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "from",
      -          "to"
      -        ],
      -        "type": "object"
      -      },
      -      "datePreset": {
      -        "description": "Widget-level period override for scheduled DIGESTs (same DatePreset as dashboards, e.g. LAST_WEEK). Omit to inherit `reportContext.datePreset` / startDate/endDate. Mutually exclusive with explicit from/to/compare — do not set both.",
      -        "enum": [
      -          "TRAILING_90_DAYS",
      -          "TRAILING_30_DAYS",
      -          "TRAILING_45_DAYS",
      -          "TRAILING_7_DAYS",
      -          "TRAILING_3_DAYS",
      -          "TRAILING_14_WEEKS",
      -          "MTD",
      -          "QTD",
      -          "YTD",
      -          "LAST_WEEK",
      -          "LAST_MONTH",
      -          "LAST_6_MONTHS",
      -          "LAST_12_MONTHS",
      -          "LAST_4_YEARS",
      -          "LAST_3_MONTHS",
      -          "LAST_INVOICE_MONTH"
      -        ],
      -        "type": "string"
      -      },
      -      "description": {
      -        "default": "",
      -        "type": "string"
      -      },
      -      "display": {
      -        "default": "tree",
      -        "description": "DIGEST presentation: \"tree\" (default, change tree only — faster), \"summary\" (LLM executive narrative — slower; opt-in AI summary), or \"table\" (flat table). Set \"summary\" when the user wants the AI write-up.",
      -        "enum": [
      -          "tree",
      -          "table",
      -          "summary"
      -        ],
      -        "type": "string"
      -      },
      -      "enableAiInvestigation": {
      -        "default": false,
      -        "description": "When true, deepest-leaf cost movers get per-node AI investigation before delivery (async analysis_agent_run — noticeably slower). Default false. Independent of display: \"summary\" (executive narrative). Prefer display: \"summary\" alone for a written overview; enable this only when the user wants deep node analysis.",
      -        "type": "boolean"
      -      },
      -      "from": {
      -        "description": "Only for one-off custom ranges (YYYY-MM-DD). Omit to inherit report context. Do not set when datePreset is set.",
      -        "format": "date",
      -        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -        "type": "string"
      -      },
      -      "limit": {
      -        "description": "Max groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.",
      -        "maximum": 1000,
      -        "minimum": 1,
      -        "type": "integer"
      -      },
      -      "minAbsoluteDiff": {
      -        "default": 100,
      -        "minimum": 0,
      -        "type": "number"
      -      },
      -      "minRelativeDiff": {
      -        "default": 5,
      -        "minimum": 0,
      -        "type": "number"
      -      },
      -      "queries": {
      -        "description": "Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (prefer short ids like a/b/c); put human labels in `alias`. On report widgets prefer sparse `{ type: \"cost\", name: \"a\", alias?, filterCel? }` and inherit metricId / currency / groupBy / period from `reportContext`. DIGEST: exactly one `{ type: \"cost\", ... }`. Call get_skill skillId: \"reports\" for examples.",
      -        "items": {
      -          "additionalProperties": {},
      -          "propertyNames": {
      -            "type": "string"
      -          },
      -          "type": "object"
      -        },
      -        "type": "array"
      -      },
      -      "scopeId": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "description": "Optional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries."
      -      },
      -      "title": {
      -        "default": "Cost changes",
      -        "type": "string"
      -      },
      -      "to": {
      -        "description": "Only for one-off custom ranges (YYYY-MM-DD). Omit to inherit report context. Do not set when datePreset is set.",
      -        "format": "date",
      -        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      -        "type": "string"
      -      },
      -      "topLargestAbsoluteChange": {
      -        "default": 20,
      -        "description": "How many largest-absolute-change nodes to surface. Allowed values: 5, 10, 15, or 20.",
      -        "type": "number"
      -      },
      -      "type": {
      -        "const": "DIGEST",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "type"
      -    ],
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "dashboardId": {
      +        "description": "Id of the dashboard to render as a PDF (from `search`/`get`).",
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "datePreset": {
      +        "description": "Optional period override for the PDF. Omit to inherit the report (and destination) period.",
      +        "enum": [
      +          "TRAILING_90_DAYS",
      +          "TRAILING_30_DAYS",
      +          "TRAILING_45_DAYS",
      +          "TRAILING_7_DAYS",
      +          "TRAILING_3_DAYS",
      +          "TRAILING_14_WEEKS",
      +          "MTD",
      +          "QTD",
      +          "YTD",
      +          "LAST_WEEK",
      +          "LAST_MONTH",
      +          "LAST_6_MONTHS",
      +          "LAST_12_MONTHS",
      +          "LAST_4_YEARS",
      +          "LAST_3_MONTHS",
      +          "LAST_INVOICE_MONTH"
      +        ],
      +        "type": "string"
      +      },
      +      "description": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "from": {
      +        "description": "Optional custom range start (YYYY-MM-DD). Use with to instead of datePreset.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "title": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "to": {
      +        "description": "Optional custom range end (inclusive, YYYY-MM-DD). Use with from instead of datePreset.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "type": {
      +        "const": "DASHBOARD_PDF",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "type",
      +      "dashboardId"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "aggBy": {
      +        "default": "Month",
      +        "description": "Time grain for the series: Hour, Day, Week, Month, or Period (default Month).",
      +        "enum": [
      +          "Hour",
      +          "Day",
      +          "Week",
      +          "Month",
      +          "Period"
      +        ],
      +        "type": "string"
      +      },
      +      "compare": {
      +        "additionalProperties": false,
      +        "description": "Add a comparison period to show cost evolution side-by-side. Omit `from`/`to` to compare against the preceding period automatically; set `chartType` to choose how it renders.",
      +        "properties": {
      +          "chartType": {
      +            "default": "WATERFALL",
      +            "description": "How the comparison renders: WATERFALL (default, contribution to change), TABLE (before/after rows), or KPI_BREAKDOWN (per-group KPI cards). Note KPI_BREAKDOWN and TABLE widgets cannot be exported as PNG via get_widget_png.",
      +            "enum": [
      +              "WATERFALL",
      +              "TABLE",
      +              "KPI_BREAKDOWN"
      +            ],
      +            "type": "string"
      +          },
      +          "enabled": {
      +            "default": true,
      +            "description": "Defaults to true — supplying `compare` at all turns the comparison on. Pass false to keep the block while disabling it (useful when echoing back a payload from `get`).",
      +            "type": "boolean"
      +          },
      +          "from": {
      +            "description": "Comparison period start date. Omit (together with `to`) to compare against the preceding period, derived from the main period.",
      +            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      +            "type": "string"
      +          },
      +          "to": {
      +            "description": "Comparison period end date. Required together with `from`; omit both to auto-derive.",
      +            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      +            "type": "string"
      +          }
      +        },
      +        "type": "object"
      +      },
      +      "datePreset": {
      +        "description": "Widget-level period override (same DatePreset as dashboards, e.g. LAST_WEEK). Omit to inherit `reportContext.datePreset` / startDate/endDate. Use either datePreset OR explicit from/to, not both.",
      +        "enum": [
      +          "TRAILING_90_DAYS",
      +          "TRAILING_30_DAYS",
      +          "TRAILING_45_DAYS",
      +          "TRAILING_7_DAYS",
      +          "TRAILING_3_DAYS",
      +          "TRAILING_14_WEEKS",
      +          "MTD",
      +          "QTD",
      +          "YTD",
      +          "LAST_WEEK",
      +          "LAST_MONTH",
      +          "LAST_6_MONTHS",
      +          "LAST_12_MONTHS",
      +          "LAST_4_YEARS",
      +          "LAST_3_MONTHS",
      +          "LAST_INVOICE_MONTH"
      +        ],
      +        "type": "string"
      +      },
      +      "description": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "from": {
      +        "description": "Widget-level start date override. Omit to inherit report context period.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "limit": {
      +        "description": "Max groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.",
      +        "maximum": 1000,
      +        "minimum": 1,
      +        "type": "integer"
      +      },
      +      "queries": {
      +        "description": "Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (prefer short ids like a/b/c); put human labels in `alias`. On report widgets prefer sparse `{ type: \"cost\", name: \"a\", alias?, filterCel? }` and inherit metricId / currency / groupBy / period from `reportContext`. DIGEST: exactly one `{ type: \"cost\", ... }`. Call get_skill skillId: \"reports\" for examples.",
      +        "items": {
      +          "additionalProperties": {},
      +          "propertyNames": {
      +            "type": "string"
      +          },
      +          "type": "object"
      +        },
      +        "type": "array"
      +      },
      +      "scopeId": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Optional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries."
      +      },
      +      "title": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "to": {
      +        "description": "Widget-level end date override (inclusive). Omit to inherit report context period.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "type": {
      +        "const": "GRAPH_SNAPSHOT",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "type"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "aggBy": {
      +        "default": "Month",
      +        "description": "Time grain for the series: Hour, Day, Week, Month, or Period (default Month).",
      +        "enum": [
      +          "Hour",
      +          "Day",
      +          "Week",
      +          "Month",
      +          "Period"
      +        ],
      +        "type": "string"
      +      },
      +      "datePreset": {
      +        "description": "Widget-level period override (same DatePreset as dashboards, e.g. LAST_WEEK). Omit to inherit `reportContext.datePreset` / startDate/endDate. Use either datePreset OR explicit from/to, not both.",
      +        "enum": [
      +          "TRAILING_90_DAYS",
      +          "TRAILING_30_DAYS",
      +          "TRAILING_45_DAYS",
      +          "TRAILING_7_DAYS",
      +          "TRAILING_3_DAYS",
      +          "TRAILING_14_WEEKS",
      +          "MTD",
      +          "QTD",
      +          "YTD",
      +          "LAST_WEEK",
      +          "LAST_MONTH",
      +          "LAST_6_MONTHS",
      +          "LAST_12_MONTHS",
      +          "LAST_4_YEARS",
      +          "LAST_3_MONTHS",
      +          "LAST_INVOICE_MONTH"
      +        ],
      +        "type": "string"
      +      },
      +      "description": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "flopN": {
      +        "default": 5,
      +        "maximum": 50,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "from": {
      +        "description": "Widget-level start date override. Omit to inherit report context period.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "limit": {
      +        "description": "Max groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.",
      +        "maximum": 1000,
      +        "minimum": 1,
      +        "type": "integer"
      +      },
      +      "queries": {
      +        "description": "Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (prefer short ids like a/b/c); put human labels in `alias`. On report widgets prefer sparse `{ type: \"cost\", name: \"a\", alias?, filterCel? }` and inherit metricId / currency / groupBy / period from `reportContext`. DIGEST: exactly one `{ type: \"cost\", ... }`. Call get_skill skillId: \"reports\" for examples.",
      +        "items": {
      +          "additionalProperties": {},
      +          "propertyNames": {
      +            "type": "string"
      +          },
      +          "type": "object"
      +        },
      +        "type": "array"
      +      },
      +      "scopeId": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Optional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries."
      +      },
      +      "title": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "to": {
      +        "description": "Widget-level end date override (inclusive). Omit to inherit report context period.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "topN": {
      +        "default": 5,
      +        "maximum": 50,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "type": {
      +        "const": "TOP_FLOP",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "type"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "contentMarkdown": {
      +        "maxLength": 10000,
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "description": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "title": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "type": {
      +        "const": "TEXT",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "type",
      +      "contentMarkdown"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "additionalGroupBy": {
      +        "default": [],
      +        "description": "Deeper DIGEST hierarchy levels AFTER the root groupBy, in order. Root axis is `reportContext.groupBy` (preferred) or `queries[0].groupBy` when overriding. Example: reportContext.groupBy=\"cos_environment\" + additionalGroupBy=[\"cos_sub_account_id\",\"cos_service_name\"] → tree path environment → project → service.",
      +        "items": {
      +          "minLength": 1,
      +          "type": "string"
      +        },
      +        "type": "array"
      +      },
      +      "aggBy": {
      +        "default": "Month",
      +        "description": "DIGEST time grain: Week or Month (default Month). Never Period (use TOP_FLOP for whole-period movers).",
      +        "enum": [
      +          "Week",
      +          "Month"
      +        ],
      +        "type": "string"
      +      },
      +      "compare": {
      +        "description": "Required only with explicit from/to when not using datePreset or reportContext. Prefer datePreset / reportContext.",
      +        "properties": {
      +          "from": {
      +            "description": "Comparison period start date.",
      +            "format": "date",
      +            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +            "type": "string"
      +          },
      +          "to": {
      +            "description": "Comparison period end date.",
      +            "format": "date",
      +            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "from",
      +          "to"
      +        ],
      +        "type": "object"
      +      },
      +      "datePreset": {
      +        "description": "Widget-level period override for scheduled DIGESTs (same DatePreset as dashboards, e.g. LAST_WEEK). Omit to inherit `reportContext.datePreset` / startDate/endDate. Mutually exclusive with explicit from/to/compare — do not set both.",
      +        "enum": [
      +          "TRAILING_90_DAYS",
      +          "TRAILING_30_DAYS",
      +          "TRAILING_45_DAYS",
      +          "TRAILING_7_DAYS",
      +          "TRAILING_3_DAYS",
      +          "TRAILING_14_WEEKS",
      +          "MTD",
      +          "QTD",
      +          "YTD",
      +          "LAST_WEEK",
      +          "LAST_MONTH",
      +          "LAST_6_MONTHS",
      +          "LAST_12_MONTHS",
      +          "LAST_4_YEARS",
      +          "LAST_3_MONTHS",
      +          "LAST_INVOICE_MONTH"
      +        ],
      +        "type": "string"
      +      },
      +      "description": {
      +        "default": "",
      +        "type": "string"
      +      },
      +      "display": {
      +        "default": "tree",
      +        "description": "DIGEST presentation: \"tree\" (default, change tree only — faster), \"summary\" (LLM executive narrative — slower; opt-in AI summary), or \"table\" (flat table). Set \"summary\" when the user wants the AI write-up.",
      +        "enum": [
      +          "tree",
      +          "table",
      +          "summary"
      +        ],
      +        "type": "string"
      +      },
      +      "enableAiInvestigation": {
      +        "default": false,
      +        "description": "When true, deepest-leaf cost movers get per-node AI investigation before delivery (async analysis_agent_run — noticeably slower). Default false. Independent of display: \"summary\" (executive narrative). Prefer display: \"summary\" alone for a written overview; enable this only when the user wants deep node analysis.",
      +        "type": "boolean"
      +      },
      +      "from": {
      +        "description": "Only for one-off custom ranges (YYYY-MM-DD). Omit to inherit report context. Do not set when datePreset is set.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "limit": {
      +        "description": "Max groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.",
      +        "maximum": 1000,
      +        "minimum": 1,
      +        "type": "integer"
      +      },
      +      "minAbsoluteDiff": {
      +        "default": 100,
      +        "minimum": 0,
      +        "type": "number"
      +      },
      +      "minRelativeDiff": {
      +        "default": 5,
      +        "minimum": 0,
      +        "type": "number"
      +      },
      +      "queries": {
      +        "description": "Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (prefer short ids like a/b/c); put human labels in `alias`. On report widgets prefer sparse `{ type: \"cost\", name: \"a\", alias?, filterCel? }` and inherit metricId / currency / groupBy / period from `reportContext`. DIGEST: exactly one `{ type: \"cost\", ... }`. Call get_skill skillId: \"reports\" for examples.",
      +        "items": {
      +          "additionalProperties": {},
      +          "propertyNames": {
      +            "type": "string"
      +          },
      +          "type": "object"
      +        },
      +        "type": "array"
      +      },
      +      "scopeId": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Optional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries."
      +      },
      +      "title": {
      +        "default": "Cost changes",
      +        "type": "string"
      +      },
      +      "to": {
      +        "description": "Only for one-off custom ranges (YYYY-MM-DD). Omit to inherit report context. Do not set when datePreset is set.",
      +        "format": "date",
      +        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      +        "type": "string"
      +      },
      +      "topLargestAbsoluteChange": {
      +        "default": 20,
      +        "description": "How many largest-absolute-change nodes to surface. Allowed values: 5, 10, 15, or 20.",
      +        "type": "number"
      +      },
      +      "type": {
      +        "const": "DIGEST",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "type"
      +    ],
      +    "type": "object"
      +  }
      +]
  3. First observed

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial side-effect disclosure beyond the spartan annotations: NOW 'sends real messages now' and SCHEDULED 'authorizes future channel delivery,' both flagged as DANGER ZONE. Also discloses the deprecated context alias, inheritance behavior, and return payload (report URL, nextRunAt, plus execution URLs and per-destination status for NOW). No contradiction — destructiveHint=false concerns data destruction, while the description transparently surfaces irreversible delivery side effects the annotation omits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Unusually long, but earns its length given 5 widget types, 3 schedule modes, nested inheritance, and a deprecation. Purpose and DANGER ZONE warnings are front-loaded, and both JSON examples are complete valid payloads rather than filler. Not a 5 because the density is overwhelming and would benefit from clearer visual hierarchy (headers or bullet separation between the schedule modes and widget types).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 10-parameter tool with nested objects and no output schema, the description covers the input model, deprecations, return values, and required confirmation steps, then externalizes remaining depth to get_skill('reports'). The schema carries parameter detail at 100%, and the examples anchor the abstract rules. Nothing an agent needs to construct a valid call is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description goes far beyond it: it dictates what belongs in reportContext ('period required when query widgets are present: prefer datePreset'), the inherit-don't-repeat rule for widget overrides, and cross-field constraints ('never send both' context/reportContext; WEEKLY requires weekday). The two complete example payloads give the agent concrete valid shapes for DIGEST and GRAPH_SNAPSHOT+TOP_FLOP that the schema alone cannot convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+structure: 'Create a report with shared reportContext and one or more widgets plus destinations.' The schedule-mode breakdown (NOW vs UNSCHEDULED vs SCHEDULED) and widget-type enumeration make the tool's scope unambiguous against siblings like update_report, archive_report, and run_report_now.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit gates: 'Call get_skill with skillId: "reports" first' and a mandatory confirmation step before NOW/SCHEDULED modes ('ask the user to confirm before calling'). Mode selection semantics are well-specified, but it never explicitly names siblings like preview_report_widget or run_report_now as alternatives for iterating on drafts or running existing reports ad hoc.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

TDQS

A4.1/5.0
Disambiguation4/5

Tools are organized by resource (alerts, dashboards, reports, events, virtual dimensions) with distinct actions, so most are clearly separable. The main confusion risks are the three report-delivery side-effect tools (run_report_now, retry_report_execution, transfer_report_execution) and the generic get that spans five resource types, though detailed descriptions mitigate these.

Naming Consistency4/5

The dominant verb_noun pattern (create_*, list_*, update_*, preview_*, get_*) is consistent and predictable across the set. Deviations like bare verbs query/search/get and the noun-only virtual_dimension_overlap_matrix are readable but break the otherwise uniform convention.

Tool Count3/5

44 tools is heavy and exceeds the comfortable range, but the server covers a genuinely broad FinOps platform spanning querying, dashboards, reports, alerts, events, virtual dimensions, docs, skills, and suggestions. Each tool has a distinct job, though the sheer count makes agent navigation harder.

Completeness3/5

Core workflows are well covered: query → dashboard/report/alert/event, plus a full virtual-dimension draft lifecycle. Notable gaps include alerts being create-only with no update/delete, no deletes for dashboards/events/published virtual dimensions, and budget management limited to query/get with no create/update.

Resources