Skip to main content
Glama

Costory: Your Finops MCP

update_alert

Destructive

Update an existing cost alert. Use when the user asks to change an existing alert's settings, condition, or destinations. Look up alertId via search / list_alerts / get / create_alert. All fields except alertId (and slug) are optional — omit a field to keep the stored value. Most edits (name, description, tagIds, condition, dedup) need nothing else. tagIds replaces the full list: existing IDs from list_tags and/or { name, color? } for new tags; color defaults to #6366F1; missing tags are created. Omit keeps stored tags; [] clears them. Explorer (queries + period) and notification dest are wholesale groups: if you send any field in the group, send the full group, written from scratch exactly as for create_alert — the stored queries from get are not a valid input. Inside the explorer group, omit scopeId to keep the alert's saved scope (send null to clear it); it cannot be sent on its own. scopeId is the same id as list_teams, stored on the alert (read it from get) and not merged into the stored queries. Do not send aggBy: the stored grain is always Day. Setting condition on a legacy threshold alert that has no dedup config succeeds but the response may include warning: pass dedup too (or in a follow-up call) to avoid notifying on every firing evaluation. Sharing is not this tool — use get_object_permissions then set_object_permissions with resourceKind "costAlert". messageWidgets replaces the message-widget list wholesale (exactly one ALERT widget required; no DIGEST on alerts); omit to leave the stored widgets unchanged. To edit, read the current list via get: each entry carries config, a lossless writable copy in the exact shape this field accepts — echo every config you are NOT changing back verbatim and edit only the entries you mean to change, so untouched widgets keep their full stored settings (the prose parameters summary is truncated and NOT writable). If any entry's config is null, its stored settings cannot be expressed here — do not replace messageWidgets then. EXAMPLE: rename → { alertId, name: "New name" } EXAMPLE: dest switch to Slack → { alertId, notificationChannel: "SLACK", slackChannelId: "C01ABC" } EXAMPLE: condition change → { alertId, condition: "a > 2000" } EXAMPLE: narrow the monitored series (scope kept) → { alertId, queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD", filterCel: "cos_provider in ["AWS"]" }], datePreset: "TRAILING_90_DAYS" }

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "alertId": "<alertId>",
      "context": "<context>"
    },
    "name": "update_alert"
  }
}

Input Schema

NameRequiredDescriptionDefault
toNoExplicit window end (inclusive, YYYY-MM-DD). Use with from instead of datePreset.
fromNoExplicit window start (YYYY-MM-DD). Use with to instead of datePreset.
nameNoDisplay name for the alert.
slugNoOrganization slug. Omit to auto-detect from your account (fails if you belong to multiple orgs).
dedupNoDeduplication config controlling how often a still-firing group notifies. The window is per groupBy value; delivery stays one message listing newly eligible groups. Either CALENDAR (kind: CALENDAR, calendarUnit: WEEK | MONTH) = at most once per current ISO week / calendar month, or ROLLING (kind: ROLLING, windowDays: N) = at most once every N days.
scoutNoScout persona NAME (from list_scouts) or its id. Null clears the assignment; omitted leaves it unchanged.
emailsNoEmail addresses (required if EMAIL)
tagIdsNoReplaces the full tag list. Omit to keep stored tags; pass [] to clear. Pass existing tag IDs from `list_tags`, and/or new tag objects `{ name, color? }` (created in the org if missing; color defaults to #6366F1).
alertIdYesAlert ID from search, list_alerts, get, or create_alert.
contextYesA few words on why you are calling this tool, for the server developer's analytics. Describe people and things by role, such as "a customer". Do not quote the user or include names, emails, or other personal details.
queriesNoReplaces the monitored series wholesale, and `condition` references these `name`s. Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (use short ids like a/b/c); put human labels in `alias`. Write them from scratch: the stored `queries` returned by the `get` tool are a different, read-only shape.
scopeIdNoTeam scope id (list_teams), stored on the alert, not merged into query filters. A change still needs full queries plus period. Omit (with queries+period) to keep the saved scope; null clears it. Do not send scopeId alone.
conditionNoAlerts v3 firing rule: a single boolean expression over the query names (`name` field of each query). Supports arithmetic (+ - * /), comparisons (> >= < <= == !=), logical and/or/not, parentheses, and these window functions: rollingSum(a, N, UNIT) (trailing sum over the last N units, UNIT ∈ DAY|WEEK|MONTH, inclusive of today), weekToDateSum(a) (Monday-to-date), monthToDateSum(a) (1st-of-month-to-date), and timeShift(a, N, UNIT) (value shifted back N units; may wrap a window function). Examples: `a > 1000`, `rollingSum(a, 7, DAY) > 1000`, `(a - timeShift(a, 1, DAY)) / timeShift(a, 1, DAY) > 0.2`, `a > 10000 or rollingSum(a, 7, DAY) > 50000`.
llm_modelNoThe model identifier you are running as, for example "claude-opus-4-8" or "gpt-5.2". Pass "unknown" if you are not certain. Recorded for the server developer's analytics.
datePresetNoOfficial date preset (same as Explore, e.g. MTD, LAST_MONTH). Forecast: CURRENT_MONTH / CURRENT_QUARTER / CURRENT_YEAR. Mutually exclusive with from/to.
descriptionNoAlert description.
messageWidgetsNoReplaces the alert's message-widget list wholesale (alert body plus optional TEXT / GRAPH_SNAPSHOT / TOP_FLOP / DASHBOARD_PDF extras). Omit to leave the stored list unchanged. Exactly one ALERT widget is required; DIGEST is not supported on alerts. Read the current list via `get` and echo each entry's `config` back verbatim for widgets you are not changing (the prose `parameters` summary is truncated and not writable).
slackChannelIdNoSlack target id (required if SLACK): a channel id (C…) to post to a channel, or a Slack user id (U…) to deliver a direct message to that user. Use list_available_destinations to discover both channels and the signed-in user's DM.
teamsChannelIdNoTeams channel ID (required if TEAMS)
conversation_idNoThe conversation_id this server returned earlier in this conversation, unchanged, including after later user messages. Omit it if you have none. Recorded for the server developer's analytics.
notificationChannelNoNotification channel

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed4 schema fields changed
    • changedInput schema / properties / context / description
      Previous value: -"Explain in 15-25 words, in third person, why this tool is called and how it supports the user's goal. For analytics only. You MUST describe only the abstract purpose of the tool call. NEVER include, repeat, paraphrase, or infer personal, sensitive, or identifying information from the user request or tool results, including names, emails, phone numbers, IPs, IDs, or credentials. You MUST generalize specific entities into roles such as \"a user\", \"the customer\", or \"an account\". Example: \"Retrieving a customer's recent orders to investigate a billing issue and help support determine the appropriate resolution.\""New value: +"A few words on why you are calling this tool, for the server developer's analytics. Describe people and things by role, such as \"a customer\". Do not quote the user or include names, emails, or other personal details."
    • changedInput schema / properties / queries / description
      Previous value: -"Replaces the monitored series wholesale, and `condition` references these `name`s. 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`. Write them from scratch: the stored `queries` returned by the `get` tool are a different, read-only shape."New value: +"Replaces the monitored series wholesale, and `condition` references these `name`s. Same series objects as the `query` tool `queries` array (cost / metric / usage / externalMetric / formula / budget). Each requires `type` (never omit) and a `name` (use short ids like a/b/c); put human labels in `alias`. Write them from scratch: the stored `queries` returned by the `get` tool are a different, read-only shape."
    • changedInput schema / properties / scout / description
      Previous value: -"Scout persona NAME (from list_scouts) — preferred — or its id. Null clears the assignment; omitted leaves it unchanged."New value: +"Scout persona NAME (from list_scouts) or its id. Null clears the assignment; omitted leaves it unchanged."
    • changedInput schema / required
      Previous value: -[
      -  "alertId",
      -  "context",
      -  "llm_model"
      -]New value: +[
      +  "alertId",
      +  "context"
      +]
  2. Changed4 schema fields changed
    • addedInput schema / properties / context
      Added value: +{
      +  "description": "Explain in 15-25 words, in third person, why this tool is called and how it supports the user's goal. For analytics only. You MUST describe only the abstract purpose of the tool call. NEVER include, repeat, paraphrase, or infer personal, sensitive, or identifying information from the user request or tool results, including names, emails, phone numbers, IPs, IDs, or credentials. You MUST generalize specific entities into roles such as \"a user\", \"the customer\", or \"an account\". Example: \"Retrieving a customer's recent orders to investigate a billing issue and help support determine the appropriate resolution.\"",
      +  "type": "string"
      +}
    • addedInput schema / properties / conversation_id
      Added value: +{
      +  "description": "The conversation_id this server returned earlier in this conversation, unchanged, including after later user messages. Omit it if you have none. Recorded for the server developer's analytics.",
      +  "type": "string"
      +}
    • addedInput schema / properties / llm_model
      Added value: +{
      +  "description": "The model identifier you are running as, for example \"claude-opus-4-8\" or \"gpt-5.2\". Pass \"unknown\" if you are not certain. Recorded for the server developer's analytics.",
      +  "type": "string"
      +}
    • changedInput schema / required
      Previous value: -[
      -  "alertId"
      -]New value: +[
      +  "alertId",
      +  "context",
      +  "llm_model"
      +]
  3. Changed4 schema fields changed
    • changedInput schema / properties / emails / items / pattern
      Previous value: -"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"New value: +"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
    • removedInput schema / properties / scopeId / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / scopeId / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • addedInput schema / properties / scout
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Scout persona NAME (from list_scouts) — preferred — or its id. Null clears the assignment; omitted leaves it unchanged."
      +}
  4. Changed2 schema fields changed
    • addedInput schema / definitions
      Added value: +{
      +  "AlertMcpDatePreset": {
      +    "description": "Query period preset for this widget — any preset accepted by the `query` tool or the alert's top-level `datePreset` (e.g. MTD, LAST_MONTH, TRAILING_7_DAYS, TRAILING_90_DAYS). Omit to inherit the alert's effective-day period. Mutually exclusive with from/to. Invented values are rejected.",
      +    "type": "string"
      +  },
      +  "AlertMcpFrom": {
      +    "description": "Custom range start (YYYY-MM-DD). Use with to instead of datePreset.",
      +    "type": "string"
      +  },
      +  "AlertMcpQueries": {
      +    "description": "Optional explicit query override: `queries` (same series objects as the `query` tool) plus an optional period (`datePreset` or `from`/`to`, daily grain). Omit entirely to inherit the alert's first source cost query, resolved with the alert's currency, scope, and effective-day period at send time.",
      +    "items": {
      +      "additionalProperties": {},
      +      "propertyNames": {
      +        "type": "string"
      +      },
      +      "type": "object"
      +    },
      +    "type": "array"
      +  },
      +  "AlertMcpTo": {
      +    "description": "Custom range end (inclusive, YYYY-MM-DD). Use with from instead of datePreset.",
      +    "type": "string"
      +  }
      +}
    • addedInput schema / properties / messageWidgets
      Added value: +{
      +  "description": "Replaces the alert's message-widget list wholesale (alert body plus optional TEXT / GRAPH_SNAPSHOT / TOP_FLOP / DASHBOARD_PDF extras). Omit to leave the stored list unchanged. Exactly one ALERT widget is required; DIGEST is not supported on alerts. Read the current list via `get` and echo each entry's `config` back verbatim for widgets you are not changing (the prose `parameters` summary is truncated and not writable).",
      +  "items": {
      +    "discriminator": {
      +      "propertyName": "type"
      +    },
      +    "oneOf": [
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "description": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "title": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "type": {
      +            "const": "ALERT",
      +            "description": "The triggered-alert body itself (date, condition, firing groups, explore links). Exactly one ALERT widget is required in every list.",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "type"
      +        ],
      +        "type": "object"
      +      },
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "contentMarkdown": {
      +            "description": "Markdown content, 1–10,000 characters.",
      +            "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": {
      +          "datePreset": {
      +            "$ref": "#/definitions/AlertMcpDatePreset"
      +          },
      +          "description": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "from": {
      +            "$ref": "#/definitions/AlertMcpFrom"
      +          },
      +          "queries": {
      +            "$ref": "#/definitions/AlertMcpQueries"
      +          },
      +          "title": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "to": {
      +            "$ref": "#/definitions/AlertMcpTo"
      +          },
      +          "type": {
      +            "const": "GRAPH_SNAPSHOT",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "type"
      +        ],
      +        "type": "object"
      +      },
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "comparison": {
      +            "default": {
      +              "kind": "PREVIOUS_PERIOD"
      +            },
      +            "description": "Baseline: PREVIOUS_PERIOD (default; the previous day at send time on alerts) or a CUSTOM { from, to } range.",
      +            "discriminator": {
      +              "propertyName": "kind"
      +            },
      +            "oneOf": [
      +              {
      +                "additionalProperties": false,
      +                "properties": {
      +                  "kind": {
      +                    "const": "PREVIOUS_PERIOD",
      +                    "type": "string"
      +                  }
      +                },
      +                "required": [
      +                  "kind"
      +                ],
      +                "type": "object"
      +              },
      +              {
      +                "properties": {
      +                  "from": {
      +                    "minLength": 1,
      +                    "type": "string"
      +                  },
      +                  "kind": {
      +                    "const": "CUSTOM",
      +                    "type": "string"
      +                  },
      +                  "to": {
      +                    "minLength": 1,
      +                    "type": "string"
      +                  }
      +                },
      +                "required": [
      +                  "kind",
      +                  "from",
      +                  "to"
      +                ],
      +                "type": "object"
      +              }
      +            ]
      +          },
      +          "cutOff": {
      +            "default": 0,
      +            "description": "Minimum absolute cost change for a row to qualify (default 0).",
      +            "minimum": 0,
      +            "type": "number"
      +          },
      +          "datePreset": {
      +            "$ref": "#/definitions/AlertMcpDatePreset"
      +          },
      +          "description": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "flopN": {
      +            "default": 5,
      +            "description": "Number of largest-decrease rows (default 5).",
      +            "maximum": 100,
      +            "minimum": 0,
      +            "type": "integer"
      +          },
      +          "from": {
      +            "$ref": "#/definitions/AlertMcpFrom"
      +          },
      +          "queries": {
      +            "$ref": "#/definitions/AlertMcpQueries"
      +          },
      +          "title": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "to": {
      +            "$ref": "#/definitions/AlertMcpTo"
      +          },
      +          "topN": {
      +            "default": 5,
      +            "description": "Number of largest-increase rows (default 5).",
      +            "maximum": 100,
      +            "minimum": 0,
      +            "type": "integer"
      +          },
      +          "type": {
      +            "const": "TOP_FLOP",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "type"
      +        ],
      +        "type": "object"
      +      },
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "dashboardId": {
      +            "description": "Id of the dashboard to render as a PDF (from `search`/`get`).",
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "datePreset": {
      +            "$ref": "#/definitions/AlertMcpDatePreset"
      +          },
      +          "description": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "from": {
      +            "$ref": "#/definitions/AlertMcpFrom"
      +          },
      +          "title": {
      +            "default": "",
      +            "type": "string"
      +          },
      +          "to": {
      +            "$ref": "#/definitions/AlertMcpTo"
      +          },
      +          "type": {
      +            "const": "DASHBOARD_PDF",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "type",
      +          "dashboardId"
      +        ],
      +        "type": "object"
      +      }
      +    ]
      +  },
      +  "minItems": 1,
      +  "type": "array"
      +}
  5. Changed1 schema field changed
    • changedInput schema / properties / datePreset / description
      Previous value: -"Official date preset (same DatePreset as dashboards/reports, e.g. MTD, LAST_MONTH, TRAILING_30_DAYS). Prefer this over hand-computed from/to when a preset matches. Mutually exclusive with from/to."New value: +"Official date preset (same as Explore, e.g. MTD, LAST_MONTH). Forecast: CURRENT_MONTH / CURRENT_QUARTER / CURRENT_YEAR. Mutually exclusive with from/to."
  6. Changed1 schema field changed
    • changedInput schema / properties / scopeId / description
      Previous value: -"Saved team scope id (from list_teams). Changing it still requires complete queries plus period. Omit (with queries+period) to keep the saved scope; send null to clear. Do not send scopeId alone."New value: +"Team scope id (list_teams), stored on the alert, not merged into query filters. A change still needs full queries plus period. Omit (with queries+period) to keep the saved scope; null clears it. Do not send scopeId alone."
  7. Added

TDQS

A4.9/5.0
Behavior5/5

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

Annotations declare destructiveHint=true and openWorldHint=true but say nothing about the actual mutation model; the description supplies it — omit-keeps-stored-value vs null-clears, wholesale replacement for tagIds/queries/messageWidgets, the requirement to echo untouched widget configs verbatim, and the case where condition edits silently succeed but return a `warning`. That is precisely the behavior an agent needs before overwriting stored settings.

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?

Front-loaded with purpose, then when-to-use, then field semantics, then examples — the ordering is sound for a 21-parameter tool. It is dense and occasionally restates what the schema already says (tagIds replacement, messageWidgets rules appear in both places), which is padding rather than error, so 4 rather than 5.

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 destructive, 21-parameter, no-output-schema mutation tool, the description covers the parts an agent would otherwise get wrong: replacement-vs-merge per field, the explorer group's indivisible fields, the ALERT-widget requirement, and the partial-failure `warning`. Return shape is described where it matters despite the absence of an output schema.

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 already 100%, yet the description still adds decision-level meaning the schema does not encode: interactions between groups ("if you send any field in the group, send the full group"), why stored `queries` from get are not valid input, why `scopeId` rides alongside queries rather than inside them, and where `config` comes from. The worked examples pin the semantics down concretely.

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 and resource ("Update an existing cost alert") and immediately bounds scope against siblings: sharing is explicitly routed to get_object_permissions/set_object_permissions, and alertId lookup is routed to search/list_alerts/get/create_alert. An agent can distinguish this from create_alert or the permission tools without opening a schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use ("the user asks to change an existing alert's settings, condition, or destinations") plus when-not (sharing, do not send aggBy, do not send scopeId alone, do not replace messageWidgets when any config is null). It also names the exact follow-up needed for the legacy threshold-alert case (pass dedup), which is real routing guidance rather than generic advice.

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.

Resources