Changelog & versioning
Versioning
Section titled “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: trueadds all three cloud-billed sources (Bedrock, Azure AI Foundry, Gemini / Vertex AI) to the direct providers.include_bedrock: truestill means direct + Bedrock only, and the default is still direct-only (Anthropic + OpenAI).- New
group_byvalues for a cloud-billed source:account,region,routing, andmodel_developer(Azure and Vertex). crossBilledTotalnow covers every cloud-billed source. Never addtotalCost,allAiTotal, orcrossBilledTotalto a cloud figure.get_finops_briefingwithinclude_ai_spend: trueadds a separate, informational cloud-billed AI line. It is already inside the cloud numbers — never add it to anything.
v1 wave 4.1 — server 0.5.1
Section titled “v1 wave 4.1 — server 0.5.1”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 anorganization_idfromlist_organizationsandconfirm: 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 getsnot_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, justconfirm: true.cak_API keys can’t use it, since each key is pinned to one organization. list_organizationsnow marks the current organization (is_current, andcurrentOrganizationat the top).list_organizationsandswitch_organizationwork 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 toswitch_organization.
v1 wave 4 — server 0.5.0
Section titled “v1 wave 4 — server 0.5.0”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 adeliverableflag:falsewithreason: "no_integrations"means the schedule is active but won’t send until an integration is connected.create_report_schedule: email a saved report aspersonal(to you alone) orteam(up to 5 emails plus any channels). If the report already has that schedule, you getalready_scheduledwith 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,budgetorexpiry, 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).
v1 wave 3.1 — server 0.4.1
Section titled “v1 wave 3.1 — server 0.4.1”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 fromlist_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.
v1 wave 3 — server 0.4.0
Section titled “v1 wave 3 — server 0.4.0”⚠ 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.
The gate, in full
Section titled “The gate, in full”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
freshthe 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 insectionsUnavailable; the briefing still succeeds. A section a provider cannot have (commitments for an LLM provider, savings outside AWS/Azure) reportsnot_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;
structuredContentcarries one key per section plusgeneratedAt,window,costBasisandsectionsUnavailable.
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:
resourcemust be the last dimension;- the pivot must be scoped with
integration_id— neverproviderorintegration_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.
v1 wave 2 — server 0.3.0
Section titled “v1 wave 2 — server 0.3.0”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_statusnames 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_reportremain the report tools).
- Async by nature. Both drive an Athena job.
run_cost_pivotwaits up towait_seconds(default 20, max 22) and returns rows if the job finishes, or a{ jobId, status, pollAfterMs, expiresAt }handle if not.get_cost_pivotcollects 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
requestedWindowandreturnedWindow(derived fromcolumns, the only authority) plus awindowNotewhen they disagree. limitis a client-side cap, applied after sorting by_totaldesc — the backend returns every matching row in one response and does not page.nextCursoris alwaysnullandcursoris 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 lowercaseuntagged. resourceis 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.
Unreleased — backend-contract fixes
Section titled “Unreleased — backend-contract fixes”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.
v1 — initial release
Section titled “v1 — initial release”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_costsis one flexible cost query (view=+ filters);get_savingsreturns the whole savings picture in one call;investigate_anomalychains 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_budgetdoes 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.
Shipped after v1 (additive)
Section titled “Shipped after v1 (additive)”v1 wave 1 — server 0.2.0
Section titled “v1 wave 1 — server 0.2.0”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 amethodand a plain-Englishconfidencenote (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 withnew/goneflags. Explicit windows or thewow/mom/yoypresets.investigate_anomalyis the documented follow-up for why.get_resource_breakdown— the per-resource step insideinvestigate_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?”. Afresh/stale/degradedverdict 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 theinvite_idthatcancel_inviteneeds (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 ascostBasisso 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_idnow 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_forecastandget_resource_breakdownaccept 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.
Earlier
Section titled “Earlier”- Service-account keys (
cak_). Paid organizations mint, list, and revoke keys from Settings → API Keys; presented asAuthorization: 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.
Deferred (not in v1)
Section titled “Deferred (not in v1)”- 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.
Recent additions (v1, additive)
Section titled “Recent additions (v1, additive)”- 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_statusnow acceptsnotes/realized_savings/dismissed_reason. dismiss_anomalynow requireswindow(part of the dismissal key), so a dismissal can always be reversed byundismiss_anomaly.- Hardening: JSON-RPC batch requests are rejected (they could otherwise slip
past the tier gate); every downstream call has a timeout; the DCR
/registershim validatesredirect_uris.
Upgrade / stability notes
Section titled “Upgrade / stability notes”- 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.