Skip to content

Changelog & versioning

Component Version Notes
Server / package 0.7.0 Reported by GET /health and in the MCP server identity.
Tool surface v1 · wave 4.2 65 tools (41 read + 24 write). See the Tool Reference.
MCP SDK Official @modelcontextprotocol/sdk (v1.x) Exact version pin, validated before every release.
Transport Streamable HTTP, stateless JSON-buffered responses; no SSE, no session id, no resumability.
Protocol auth OAuth 2.1 + PKCE (S256) RFC 9728 / 8414 discovery + RFC 7591 DCR shim; RFC 8707 audience binding; RFC 9207 issuer identification.

The tool surface follows MCP house conventions: readOnlyHint/destructiveHint annotations on every tool, a response_format: concise|detailed enum, limit + ~25k-token output truncation, account-id → name resolution, and onboarding-quality descriptions with example inputs.

Server 0.7.0: sign-in through mcp.cloudquell.com (ChatGPT support)

Section titled “Server 0.7.0: sign-in through mcp.cloudquell.com (ChatGPT support)”

No tool changes (65 tools). This release changes how clients sign in.

  • ChatGPT can connect. Add CloudQuell as a custom MCP server in ChatGPT and sign in with your CloudQuell account. See Connecting.
  • Claude Code connects directly with claude mcp add --transport http, with no bridge needed.
  • Sign-in now goes through mcp.cloudquell.com, which checks that the app asking to connect is a supported one before showing the CloudQuell sign-in page. Each new connection asks you to sign in, even if you’re already signed in to CloudQuell.
  • Unchanged: the same CloudQuell account and sign-in page, the same scopes and tokens, the same write gate and roles, and cak_ API keys. Existing connections keep working without reconnecting.

v1 wave 4.2 — server 0.6.0: cloud-billed AI sources in get_ai_spend

Section titled “v1 wave 4.2 — server 0.6.0: cloud-billed AI sources in get_ai_spend”

No new tools (65 total). get_ai_spend now covers two more cloud-billed AI sources next to Amazon Bedrock:

  • provider: "azure_openai" — Azure AI Foundry: Azure OpenAI, the other models Microsoft sells through Foundry, and Claude bought through Azure Marketplace, read from your Azure integration.
  • provider: "vertex" — Google Gemini / Vertex AI generative usage (Vertex AI and the Gemini API), read from your Google Cloud integration.
  • include_cross_billed: true adds all three cloud-billed sources (Bedrock, Azure AI Foundry, Gemini / Vertex AI) to the direct providers. include_bedrock: true still means direct + Bedrock only, and the default is still direct-only (Anthropic + OpenAI).
  • New group_by values for a cloud-billed source: account, region, routing, and model_developer (Azure and Vertex).
  • crossBilledTotal now covers every cloud-billed source. Never add totalCost, allAiTotal, or crossBilledTotal to a cloud figure.
  • get_finops_briefing with include_ai_spend: true adds a separate, informational cloud-billed AI line. It is already inside the cloud numbers — never add it to anything.

switch_organization — change organizations from the agent (1 new write tool, 65 total)

Section titled “switch_organization — change organizations from the agent (1 new write tool, 65 total)”

If you belong to more than one organization, the agent can now switch between them without you leaving the conversation or signing in again: list_organizations → switch_organization → any other tool.

  • switch_organization: takes an organization_id from list_organizations and confirm: true. Every later tool call acts on the new organization, and the change persists: your CloudQuell dashboard moves to it too, as if you had used the org switcher there. It returns the new organization’s name, id, tier and your role, and warns if its plan doesn’t include MCP access. You can only switch to an organization you are an active member of; anything else gets not_a_member.
  • It changes only your current organization, so it is outside the write gate: no admin:write, no AI agent access switch and no admin role needed, just confirm: true. cak_ API keys can’t use it, since each key is pinned to one organization.
  • list_organizations now marks the current organization (is_current, and currentOrganization at the top).
  • list_organizations and switch_organization work even when the current organization’s plan doesn’t include MCP access or its subscription has ended, so switching into such an organization never strands you. Every other tool is still refused there, and that message now points to switch_organization.

Report schedules and alert rules over MCP (1 new read tool, 8 new write tools, 64 total)

Section titled “Report schedules and alert rules over MCP (1 new read tool, 8 new write tools, 64 total)”

An agent can now manage everything that decides who gets notified, not just budgets and channels. Every new tool uses the same delivery pair as the budget tools, notification_channel_ids and emails, and every write sits behind the same write gate.

Report schedules (scheduled report emails):

  • list_report_schedules (read): the org-wide All Costs digest, every team schedule, and your own personal schedules. Other members’ personal schedules stay invisible, admins included. Each row has a deliverable flag: false with reason: "no_integrations" means the schedule is active but won’t send until an integration is connected.
  • create_report_schedule: email a saved report as personal (to you alone) or team (up to 5 emails plus any channels). If the report already has that schedule, you get already_scheduled with the existing id instead of a duplicate.
  • create_org_digest: creates the one All Costs digest. Its emails go to members by role, so it takes Slack or Teams channels and no addresses.
  • update_report_schedule / delete_report_schedule: change the cadence, pause or resume, swap recipients, or remove a schedule.

Alert rules (read tools already existed):

  • create_alert_rule: the Alerts page form: trend, anomaly, budget or expiry, the threshold for that type, cadence, analysis period, scope (whole organization or one connection, plus services, accounts and tags) and recipients.
  • update_alert_rule: change only what you pass; everything else on the rule is kept.
  • set_alert_rule_active / delete_alert_rule: pause, resume, or remove a rule.

list_alert_rules now also reports each rule’s pinned connection (integrationId; providerType: "all" means the whole organization), whether it is active (isActive), and why the backend paused it (autoPausedReason).

update_notification_channel — edit a channel in place (1 new write tool, 55 total)

Section titled “update_notification_channel — edit a channel in place (1 new write tool, 55 total)”

Rename a channel, point a Slack or Teams channel at a new webhook URL, or replace an email channel’s address list. The channel type can’t change. Alert rules, budgets and report emails now reference channels by id and look them up when they send, so an edit reaches every one of them on its next send, with nothing to re-save. Before this, the only way to change a destination was to delete the channel and create a new one. The tool is behind the same write gate as every other write tool.

⚠ Changed: budget delivery takes channel ids, not webhook URLs

Section titled “⚠ Changed: budget delivery takes channel ids, not webhook URLs”

create_budget and update_budget replace the object-shaped notification_channels parameter with two lists:

  • notification_channel_ids: ids from list_notification_channels. Any number, any type (email, Slack, Teams).
  • emails: up to 5 addresses to notify directly.

On update, each list replaces the stored one when you send it ([] clears it); omit a list to leave it alone. Webhook URLs can no longer be pasted onto a budget; the backend refuses them. Create the channel with create_notification_channel and attach its id instead.

get_budgets, get_budget, list_alert_rules and get_alert_rule now report who gets notified: channels (each attached channel’s name and type) and emails. The ids are in notificationChannelIds in the detailed format.

⚠ Breaking for writes: MCP writes are now OFF by default, for every organization

Section titled “⚠ Breaking for writes: MCP writes are now OFF by default, for every organization”

This includes organizations that already have paid plans and working write tools. Nothing was grandfathered in. Until an admin turns writes on, every write tool refuses with:

writes_disabled_for_org: MCP writes are turned off for this organization.
An org admin can enable them in Settings → Organization → AI agent access.

Reads are completely unaffected — no reconnect, no re-consent, no added latency. If your agents only read, you will not notice this release.

How to turn writes back on: Settings → Organization → AI agent access, in the CloudQuell app, as an org admin or super_admin.

Turn it off the same way. The setting is admin-only, and it is not reachable from an agent — update_organization_preferences refuses this specific key (see below).

Write access should be something each organization opts into explicitly, and it should be checked at the MCP server before a request reaches CloudQuell — with a clear, consistent refusal when it isn’t allowed.

Every write tool now checks four things, server-side, before any downstream call:

# Check Refusal
1 Token carries admin:write insufficient_scope
2 Organization has MCP writes enabled writes_disabled_for_org
3 Caller’s org role is admin/super_admin insufficient_role
4 Call passes confirm: true confirmation_required

If the organization switch or your role cannot be read, the write is refused with org_context_unavailable rather than attempted — the gate fails closed.

update_organization_preferences refuses to set mcp_writes_enabled: it strips the key, says so in the result, and applies the rest of your change. An agent must never be able to enable writes for itself, or the model would be circular. cak_ service keys are unaffected and remain read-only always — they are minted with the read scope alone and fail check 1 regardless.

The downstream org-role checks in CloudQuell are unchanged and remain the final authority; this gate narrows what reaches them.

get_finops_briefing — one call for “what should I look at today?” (1 new read tool, 54 total)

Section titled “get_finops_briefing — one call for “what should I look at today?” (1 new read tool, 54 total)”

The entry point for scheduled and headless agents, and for a Monday-morning review. It calls up to eight existing read tools in parallel and joins them into one briefing, in a fixed order: freshness, spend, movers, anomalies, budgets, commitments, savings, and — only when include_ai_spend: true — direct-provider AI spend.

  • Freshness is reported first, on purpose. When the verdict is not fresh the briefing opens with a warning, so every number below it carries its trust level.
  • Every section degrades independently. A section that fails or times out comes back as { status: "unavailable", reason } and is named in sectionsUnavailable; the briefing still succeeds. A section a provider cannot have (commitments for an LLM provider, savings outside AWS/Azure) reports not_applicable. Never read a missing section as zero.
  • Each section drills down with its named tool — get_data_freshness, get_forecast, compare_periods, list_anomalies, get_budget_status, get_expiring_commitments, get_savings, get_ai_spend.
  • The text output is a compact markdown briefing; structuredContent carries one key per section plus generatedAt, window, costBasis and sectionsUnavailable.

period is weekly (default; month-to-date vs the same days last month) or daily (last 7 days vs the 7 before). Cap every ranked list with items_per_section (default 5, max 10).

There is also a new cloudquell-weekly-briefing skill / MCP prompt that runs the briefing and drills into whatever crosses a threshold. It performs no writes.

run_cost_pivot now supports the resource dimension

Section titled “run_cost_pivot now supports the resource dimension”

Break any pivot down to individual resources — “which instances is this service’s spend actually on”. Four rules, all checked before the job is submitted so you get a sentence instead of an error code and a wasted query:

  • resource must be the last dimension;
  • the pivot must be scoped with integration_id — never provider or integration_ids, since resource ids are only meaningful within one connection;
  • at most 2 other dimensions before it;
  • a monthly window covers at most 3 months, not the usual 6.

Resource rows may carry _resource_id and _region — both are optional and independent, verified against prod: a row can have both, either or neither in the same response, and a missing _resource_id means the backend attributed none. The displayed resource value is sometimes the full ARN and sometimes a short name; it is the empty string "" for spend with no resource attribution (support, commitment fees, tax), and the literal "Others" for the collapse bucket — which can itself carry those keys, so detect it by the string.

For the single-service, single-day question, get_resource_breakdown is still the better tool: it takes a service and a date directly and runs no job.

months_back now narrows a pivot window (backend fix)

Section titled “months_back now narrows a pivot window (backend fix)”

Previously months_back was ignored on /cost-pivot-v2/jobs: the grain clamp overwrote an absent start_date with the grain floor, so months_back: 2 came back with seven monthly columns. Fixed in cost-reporting on 2026-09-06 and verified live — the same request now returns exactly three columns (two completed months plus the current MTD).

No action needed. The MCP server always translated months_back into an explicit start_date and continues to, so run_cost_pivot callers were never affected; the translation is now redundant and deliberately kept.

Pivots. run_cost_pivot + get_cost_pivot (2 new read tools, 53 total).

Arbitrary group-by breakdowns — by product, usage type, tag, region, cost centre, charge type or provider — at monthly / weekly / daily granularity. This closes the gap left in 0.2.0, where query_costs view="detailed" began returning pivotsAvailable: false and compare_periods could only group by service or account.

These supersede the deferred generate_report / get_report_job_status names from the original plan. Same underlying cost-pivot-v2 job API; the pivot-shaped names describe what the tools actually do, and neither generates a saved report (list_saved_reports / get_report remain the report tools).

  • Async by nature. Both drive an Athena job. run_cost_pivot waits up to wait_seconds (default 20, max 22) and returns rows if the job finishes, or a { jobId, status, pollAfterMs, expiresAt } handle if not. get_cost_pivot collects the handle. Jobs are org-scoped and expire after 24 hours; there is no cancel tool.
  • Both windows are always echoed. The backend clamps silently, so every response carries requestedWindow and returnedWindow (derived from columns, the only authority) plus a windowNote when they disagree.
  • limit is a client-side cap, applied after sorting by _total desc — the backend returns every matching row in one response and does not page. nextCursor is always null and cursor is inert.
  • RESPONSE_TOO_LARGE (413) is the documented main failure mode for a wide pivot, with the correct remedy: fewer dimensions or a shorter window.
  • Rows keep the backend’s key shape verbatim, so a tag pivot row carries a literal "tag:user_app" key. Tag keys are case- and hyphen-sensitive and the untagged bucket is lowercase untagged.
  • resource is not exposed in this wave — its scope and ordering restrictions warrant a separate tool.

Cross-links. query_costs view="detailed", compare_periods, get_tag_coverage and get_allocation_breakdown now point at run_cost_pivot with concrete examples, and the spend-review, allocation-hygiene and anomaly-triage skills use it.

Verified against production. Both tools were checked against live data. Note that months_back cannot narrow a window; the tools translate it into an explicit start_date.

cost_basis is now LAYERED, not all-or-nothing. CloudQuell now loads the organization’s saved per-integration cost settings first and overrides only the toggles a request actually sends; an omitted toggle keeps its saved value instead of snapping to the API default. The costBasis echo mirrors that: source is organization_defaults when nothing was pinned and organization_defaults+overrides otherwise, with a new overrides array naming the pinned toggles. A null flag means “saved, and not visible to this server” — it never means false.

get_data_freshness reads the ingestion endpoint’s declared scope. GET /api/integrations/{integration_id}/ingestion-status now returns provider and scope (aws_only | generic). For a non-AWS connection it nulls the AWS-only counter blocks instead of zeroing them, derives status from the integration’s last_sync_at, and explains itself with a note — the zeros used to read as “never ingested” for an Azure/GCP/Snowflake/LLM connection that had been syncing for weeks. The tool now reports ingestionDetail: null plus ingestionDetailNote on those, and drops its provider heuristic. lastSyncAt from GET /api/integrations remains the authoritative verdict either way. The gateway path parameter was renamed {provider_id} → {integration_id} to match what it has always carried.

get_resource_breakdown / investigate_anomaly report the window they covered. granularity defaults to "monthly" server-side, so a bare date resolves to that whole calendar month — and the tool used to describe the window by re-deriving the backend’s arithmetic from the request. The backend now states it: ?resolved_window=true returns { resolvedWindow: {granularity, start, end}, resources } (both dates inclusive) instead of the bare row array. Tools surface it as resolvedWindow / resourceWindow and narrate from it; against an older backend the derived text is flagged as such rather than asserted.

query_costs no longer takes group_by, and the backend 400s on dimensions. dimensions was parsed into the cost-dashboard route’s QueryFilters and then never read by any view’s SQL — a caller passing it got a 200 and a payload that was not pivoted, with nothing saying so. The route now rejects it with a 400 naming the cost-pivot-v2 job API. group_by on query_costs existed only to feed that parameter and is removed. compare_periods keeps its own group_by (service / account), which is real.

query_costs { view: "detailed" } returns what the backend actually sends. byProduct, byUsageType, byTag and byAccountMonth were projected out of a payload.detailedViewData key that the cost-dashboard backend has never sent — the dashboard builds that object client-side — so all four came back null on every call, with no error. They are removed from the response, the output schema and the tool description. The view now returns exactly availableServices, availableTagKeys and availableLinkedAccounts, plus pivotsAvailable: false and a note saying that product / usage-type / tag / account pivots are served by the cost-pivot-v2 job API and are not yet exposed through MCP.

Read tools (29): query_costs, get_ai_spend, get_savings, get_recommendation_history, list_anomalies, investigate_anomaly, get_weekly_cost_slices, list_anomaly_dismissals, get_budgets, get_budget, get_budget_status, list_commitments, get_expiring_commitments, get_tag_coverage, get_allocation_breakdown, list_cost_centers, list_allocation_rules, list_saved_reports, get_report, list_alert_rules, get_alert_rule, list_triggered_alerts, list_integrations, get_integration, list_notification_channels, get_metadata, get_available_services, get_available_tag_keys, get_available_linked_accounts.

Write tools (14, require admin:write + confirm:true): create_budget, update_budget, delete_budget, update_recommendation_status, dismiss_anomaly, undismiss_anomaly, trigger_integration_ingest, create_notification_channel, delete_notification_channel, test_notification_channel, send_invite, cancel_invite, update_organization_name, update_organization_preferences.

Notable design points in v1:

  • Workflow-shaped tools, not 1:1 endpoint mirrors. query_costs is one flexible cost query (view= + filters); get_savings returns the whole savings picture in one call; investigate_anomaly chains three reads into a single investigation.
  • Anomalies are derived client-side from month-over-month / week-over-week movement — the backend exposes no precomputed anomaly list.
  • create_budget does not accept a currency — the backend stamps the org’s display currency.
  • Stable public endpoint. Clients connect at the friendly custom domain https://mcp.cloudquell.com/mcp (live; TLS cert + API Gateway mapping + DNS all in place). The OAuth resource/audience identifier matches it, so this is the canonical URL for every client.

Eight new read tools, one new query_costs parameter, MCP prompts, and structured output. Entirely additive: no tool was renamed, no parameter changed meaning, and no write tool was added — the surface is still 14 writes.

New read tools (8):

  • get_forecast — projected month-end spend, org-wide plus per-service and per-account. A projection computed here from ingested daily data, not a backend forecast; it uses the same algorithm and anchors as the dashboard’s “Forecasted Month-End” KPI, so the two agree. Every answer carries a method and a plain-English confidence note (e.g. “only 4 days of data”) so a three-day extrapolation is never presented as a fact.
  • compare_periods — the “what changed?” tool. Two windowed queries in parallel, joined into a movers table ranked by the size of the change with new/gone flags. Explicit windows or the wow / mom / yoy presets. investigate_anomaly is the documented follow-up for why.
  • get_resource_breakdown — the per-resource step inside investigate_anomaly, exposed standalone: which individual resources drove a service’s spend, with each one’s share and its prior-period cost.
  • get_data_freshness — “can I trust these numbers?”. A fresh / stale / degraded verdict with a one-line reason, plus per-connection last-ingest and staleness. Run it first in any workflow that shows a number to a person.
  • list_organizations, get_current_organization, list_members, list_invites — organization context, the human roster, and the invite_id that cancel_invite needs (it was previously unguessable).

query_costs changes (additive):

  • cost_basis — pin the accounting basis for a call (use_amortized, include_credits, include_discounts, include_refunds, include_taxes). The applied basis comes back as costBasis so an agent can state it. ⚠ It is all-or-nothing: setting any field replaces the organization’s saved settings for that call and every omitted field takes the API default.
  • account_id now accepts a 12-digit account id as well as a name. The backend filters on the sub-account name, so an id used to return silently empty results; ids are now resolved automatically. get_forecast and get_resource_breakdown accept both too.

MCP prompts. The five FinOps skills are now served as MCP prompts (cloudquell-spend-review, -anomaly-triage, -budget-guardrails, -savings-actions, -allocation-hygiene), each taking optional period and provider arguments. Any client that lists prompts gets the workflows with no install step; the skill zips remain available for skill-aware clients.

Structured output. query_costs, get_forecast, compare_periods, get_weekly_cost_slices and get_ai_spend declare an outputSchema and return structuredContent alongside the text. The text is byte-identical to before — existing clients see no change.

Known limitation. compare_periods groups by service or account only. Product / usage-type / tag pivots have no windowed aggregation on the cost-dashboard data source, so they are deliberately not offered rather than returning silent zeros.

  • Service-account keys (cak_). Paid organizations mint, list, and revoke keys from Settings → API Keys; presented as Authorization: Bearer cak_… (read-only, org-scoped, 1-year validity). See API keys.
  • FinOps skills pack. Five installable Agent Skills (spend review, anomaly triage, budget guardrails, savings actions, allocation hygiene) — packaged workflow runbooks validated against the v1 tool surface. Source in skills/; customer downloads at docs.cloudquell.com.
  • Async report generation — both creating a report job (generate_report, a write) and polling its status (get_report_job_status) are deferred; they’ll ship together so an agent can start a job and track it end-to-end.
  • Unit economics (get_unit_cost) and resource-level waste listing — under consideration for a later wave.
  • Companion reads that unblock the writes: list_notification_channels, list_integrations, get_integration, list_anomaly_dismissals, get_recommendation_history — so an agent can discover the ids the write tools need rather than guessing.
  • New writes: update_budget, delete_budget, undismiss_anomaly; update_recommendation_status now accepts notes / realized_savings / dismissed_reason.
  • dismiss_anomaly now requires window (part of the dismissal key), so a dismissal can always be reversed by undismiss_anomaly.
  • Hardening: JSON-RPC batch requests are rejected (they could otherwise slip past the tier gate); every downstream call has a timeout; the DCR /register shim validates redirect_uris.
  • The SDK version is pinned exactly to keep releases reproducible and insulate the stateless transport from patch-level regressions. Any bump is validated against MCP Inspector before release.
  • Tool names and scopes are stable within v1. New tools are additive; if a tool’s behavior changes materially it will be noted here.