Tools
The server registers 18 tools, 2 of which write. This page is generated from mcp-manifest.json, the surface the running server advertises, so the names, parameters, enums and defaults below are the ones it registers. The text under each heading is what a model reads verbatim when it picks between them.
Conventions
Of the 18, 16 are annotated readOnlyHint true, destructiveHint false, idempotentHint true, openWorldHint false: they read, and the same arguments return the same answer. The other 2 write, marked below, and carry readOnlyHint false so your client asks you before calling one. Each records a decision on a LevelFour row without changing a cloud resource.
No tool takes a tenant, an organization or an account argument. The organization comes from the credential, because it is a separate Postgres database rather than a filter on a shared one. See Connecting a client.
provider is a plain string, not an enum. Which providers exist is a fact about your organization, so the server validates the value at call time and names the valid ones in the error. Read levelfour://providers or call get-identity first.
A default of none means the parameter is optional and omitted, and the description says what the server does without it. Dates are YYYY-MM-DD. Every page_size and limit caps at 50, and a list tool returns has_next_page and next_page already computed.
Results come back as text rather than a structured content block. Reading a result has the shapes, the pagination envelope every list shares, and what truncation and errors look like.
| Tool | Mode | Purpose |
|---|---|---|
decide-recommendation | write | Record the user's decision to accept or reject one savings recommendation. |
get-commitment-overview | read | Reserved Instance and Savings Plan position, in aggregate. |
get-cost-breakdown | read | Break spend down into its largest components over a period. |
get-cost-forecast | read | Projected spend for the end of the current month. |
get-cost-growth | read | Which services grew the most, and by how much. |
get-cost-series | read | Daily spend over a date range, as a time series. |
get-cost-summary | read | Current cloud spend and where it is heading, or one month's total. |
get-costs-by-tag | read | Spend grouped by the value of a cost-allocation tag, for showback and chargeback. |
get-identity | read | Identify the connected LevelFour organization and what it has access to. |
get-potential-savings | read | Total unbooked opportunity across all recommendations, with per-status counts. |
get-realized-savings | read | Savings already realized and booked, with who approved them. |
get-recommendation | read | The full case for one savings recommendation. |
get-usage-costs | read | Billable quantity and cost per unit, grouped by usage type. |
list-accounts | read | The cloud accounts connected to this organization. |
list-anomalies | read | Detected cost anomalies, meaning unexpected spikes. |
list-commitments | read | The individual Reserved Instances and Savings Plans, with their end dates. |
list-recommendations | read | Savings opportunities LevelFour has found, largest first. |
update-anomaly-status | write | Record what the user decided about one detected cost anomaly. |
decide-recommendation
read-write key, and your client asks before calling it.Purpose: Record the user's decision to accept or reject one savings recommendation.
Returns: the recommendation's new status and the decision as it was recorded.
When to use: Only when the user has told you, in this conversation, which recommendation to accept or reject and named it by id. Open it with get-recommendation and put the monthly saving and the operational impact in front of them first.
NOT for: Making the change. Accepting books a decision and stamps the approver; it does not resize, delete or modify anything. Execution runs from the LevelFour dashboard or l4 rec execute. Not for reversing a decision either, which only the dashboard can do.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
decision | accepted, rejected | required | 'accepted' books the recommendation as a decision to make the change. 'rejected' closes it. Neither touches a cloud resource. |
explanation | string | none | Free text backing the rejection reason, used when reason is 'other'. |
reason | operational, strategy, not_applicable, other | none | Why it was rejected. Only meaningful with decision='rejected', and optional even then: the user can add it later in the dashboard. Use 'other' with explanation when none of the fixed reasons fit. |
recommendation_id | string | required | Recommendation id from list-recommendations, for example REC-1234. |
get-commitment-overview
Purpose: Reserved Instance and Savings Plan position, in aggregate.
Returns: coverage, utilization and the per-service split, plus how many commitments expire soon.
When to use: "How well are we covered." For the individual commitments and their expiry dates, follow with list-commitments.
NOT for: On-demand spend, use get-cost-breakdown.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. This tool reports on one provider at a time: omit it and the organization's first connected provider is used, which is not the whole bill for a multi-cloud organization. Call get-identity to see which are available. |
get-cost-breakdown
Purpose: Break spend down into its largest components over a period.
Returns: a paginated, sorted list of cost lines with amounts and shares.
When to use: After get-cost-summary, to answer "what is driving it". Ask one broad question first rather than one call per service. The window is one calendar month unless start_date and end_date are both given.
Examples: top services this month: no arguments. Largest accounts in July: period="2026-07", sort_by="account_id".
NOT for: Explaining a change over time, use get-cost-growth. Dating when a jump started, use get-cost-series. Savings opportunities, use list-recommendations.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
end_date | string, YYYY-MM-DD | none | End of the range, YYYY-MM-DD. Takes effect only together with start_date: on its own it is ignored and the month in period is reported instead. |
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
period | string | none | Month to report on, YYYY-MM. Omit for the current month, or pass start_date and end_date together to report on an arbitrary window instead. |
provider | string | none | Cloud provider id, for example aws or gcp. Omit and the breakdown covers AWS only, which is not the whole bill for a multi-cloud organization. Call get-identity to see which are available. |
sort_by | cost, service, region, account_id, change_percentage | "cost" | Field to sort by. |
sort_order | asc, desc | "desc" | Sort direction. |
start_date | string, YYYY-MM-DD | none | Start of the range, YYYY-MM-DD. Takes effect only together with end_date: on its own it is ignored and the month in period is reported instead. |
get-cost-forecast
Purpose: Projected spend for the end of the current month.
Returns: the forecast figure, its confidence range, and the basis it was derived from: how much is already booked, the daily run rate, and how many days are left to project.
When to use: "Where will we land this month."
NOT for: Historical spend, use get-cost-summary. A horizon beyond this month, which LevelFour does not forecast. LevelFour has no budget object either, so if the user asks whether they will exceed a budget you must ask them for the number.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. This tool reports on one provider at a time: omit it and the organization's first connected provider is used, which is not the whole bill for a multi-cloud organization. Call get-identity to see which are available. |
get-cost-growth
Purpose: Which services grew the most, and by how much.
Returns: services ranked by how many dollars they added, with the percentage beside each, plus the two windows compared and how many days each covers, so a partial month is visible rather than silent.
When to use: "Why did the bill go up." Keep the default compare_to unless the user asked specifically about calendar months.
NOT for: Absolute spend ranking, use get-cost-breakdown. Unexpected spikes specifically, use list-anomalies.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
compare_to | previous_period, previous_month | "previous_period" | Baseline window. 'previous_period' compares the last 30 days against the 30 before them and is the reliable default. 'previous_month' compares this month so far against the whole of last month, so early in a month it understates growth. |
limit | integer, 1 to 50 | 10 | Maximum rows to return. |
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. This tool reports on one provider at a time: omit it and the organization's first connected provider is used, which is not the whole bill for a multi-cloud organization. Call get-identity to see which are available. |
get-cost-series
Purpose: Daily spend over a date range, as a time series.
Returns: one point per day with the total for that day.
When to use: To date a step change. When the bill jumped and you need to know which day it started, this is the only tool that can tell you. Pair it with get-cost-breakdown over the same window to attribute the jump.
NOT for: Month totals, use get-cost-summary. Splitting a day by service, use get-cost-breakdown for that window.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
end | string, YYYY-MM-DD | none | End of the range, YYYY-MM-DD. Omit for today. |
start | string, YYYY-MM-DD | none | Start of the range, YYYY-MM-DD. Omit for the first of the current month. |
get-cost-summary
Purpose: Current cloud spend and where it is heading, or one month's total.
Returns: with a period, that month's spend and its change on the previous month, and nothing else. Without one: total spend, per-provider totals, month-over-month change and the monthly series.
When to use: The opening question, "what are we spending". For "what did we spend in July", pass period="2026-07" rather than reading it out of the series.
NOT for: Spend split by service, account or tag, use get-cost-breakdown. A day-level series, use get-cost-series.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
period | string | none | Month to report, YYYY-MM. Pass it when the question names one month, and the answer is that month's total and its change on the month before. Omit it for the current position, which also carries the forecast, potential savings and the monthly series. |
get-costs-by-tag
Purpose: Spend grouped by the value of a cost-allocation tag, for showback and chargeback.
Returns: spend per tag value, plus allocation coverage showing how much spend carries the tag. Called with no tag_key, returns the tag keys available so you can pick one.
When to use: "What does team X cost", or any per-team, per-environment or per-owner question. Read the coverage figure before reporting a total: untagged spend is not zero spend.
Examples: discover keys: no arguments. Then: tag_key="team".
NOT for: Per-service or per-account splits, use get-cost-breakdown. Waste per team, which no tool can answer directly because recommendations do not carry tags.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
end | string, YYYY-MM-DD | none | End of the range, YYYY-MM-DD. Omit for today. |
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. This tool reports on one provider at a time: omit it and the organization's first connected provider is used, which is not the whole bill for a multi-cloud organization. Call get-identity to see which are available. |
start | string, YYYY-MM-DD | none | Start of the range, YYYY-MM-DD. Omit for the first of the current month. |
tag_key | string | none | Cost allocation tag key to group by. Call with no tag_key to list the keys available. |
get-identity
Purpose: Identify the connected LevelFour organization and what it has access to.
Returns: organization plan, the cloud providers that have data, and remaining evaluation runs.
When to use: To check the connection works, or when a provider argument was rejected and you need the valid values. You do not need to call this before every analysis.
NOT for: Cloud account ids or names, use list-accounts. Spend figures, use get-cost-summary.
Parameters
Takes no arguments.
get-potential-savings
Purpose: Total unbooked opportunity across all recommendations, with per-status counts.
Returns: open potential savings split by provider, and a per-status breakdown of the whole catalog. The per-status figures are separate quantities and must not be summed: optimized is work already applied, unavailable is locked by the plan.
When to use: "How much could we save in total", or to size the backlog before listing it.
NOT for: Savings already achieved, use get-realized-savings. Individual opportunities, use list-recommendations.
Parameters
Takes no arguments.
get-realized-savings
Purpose: Savings already realized and booked, with who approved them.
Returns: realized totals, ROI and payback, plus a line-item history.
When to use: "How much have we actually saved", or any question about past results. Each ledger row carries a full monthly rate, so a sum across rows is a run rate, not a period total.
NOT for: Opportunities not yet applied, use get-potential-savings. Realized and potential savings must never be added together.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
account_id | array of string | none | Restrict to these values. Omit for all. |
end | string, YYYY-MM-DD | none | End of the range, YYYY-MM-DD. Omit to search up to the most recent record. |
environment | array of string | none | Restrict to these values. Omit for all. |
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. Omit for every provider this organization has. Call get-identity if you are unsure which are available. |
service | array of string | none | Restrict to these values. Omit for all. |
start | string, YYYY-MM-DD | none | Start of the range, YYYY-MM-DD. Omit to search from the earliest record. |
get-recommendation
Purpose: The full case for one savings recommendation.
Returns: financials, affected resources, risk assessment, metrics, the implementation method, an as_of date for the figures, and any IAM grant the change needs. The grant is here because a customer has to authorize the apply; the structured operator artifacts that carry it a second time are omitted.
When to use: After list-recommendations, when the user asks about a specific id.
NOT for: Making the change. To record the user's accept or reject, use decide-recommendation; running the change itself happens in the LevelFour dashboard or with the l4 CLI. Not for scanning the backlog either, use the list tool.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
recommendation_id | string | required | Recommendation id from list-recommendations, for example REC-1234. |
get-usage-costs
Purpose: Billable quantity and cost per unit, grouped by usage type.
Returns: a paginated list of spend, quantity, unit and derived unit cost per usage type, largest first. A row whose quantity is not a countable unit carries no derived rate.
When to use: To ground a savings claim in measured consumption, or to tell a price change apart from a volume change.
NOT for: Spend totals, use get-cost-breakdown. Note that usage quantity, not cost, is the reliable idleness signal here, because the cost columns round to cents.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
end | string, YYYY-MM-DD | none | End of the range, YYYY-MM-DD. Omit for today. |
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
provider | string | none | Cloud provider id, for example aws or gcp, or 'all'. Both 'all' and omitting it read the AWS usage tables, so name gcp explicitly to see GCP usage. Call get-identity to see which are available. |
start | string, YYYY-MM-DD | none | Start of the range, YYYY-MM-DD. Omit for the first of the current month. |
list-accounts
Purpose: The cloud accounts connected to this organization.
Returns: account id, name, provider, region, connection status, and whether detailed billing data is flowing for each.
When to use: To turn an account id seen in a cost or recommendation row into a name, to answer "which accounts are connected", or to check why an account has no data.
NOT for: Spend per account, use get-cost-breakdown with group_by="account_id".
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
list-anomalies
Purpose: Detected cost anomalies, meaning unexpected spikes.
Returns: anomalies with service, magnitude, detection time and status, plus counts by severity.
When to use: "Did anything spike", or investigating an unexpected increase in a past month. With no start and end this spans every anomaly ever detected, so pass both to ask about one month. The window is matched against the day a spike began, not the day it was detected.
NOT for: Expected or gradual growth, use get-cost-growth.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
end | string, YYYY-MM-DD | none | End of the range, YYYY-MM-DD. Omit to search up to the most recent record. |
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. Omit for every provider this organization has. Call get-identity if you are unsure which are available. |
severity | critical, warning | none | Restrict to one severity. Omit for all. |
start | string, YYYY-MM-DD | none | Start of the range, YYYY-MM-DD. Omit to search from the earliest record. |
status | active, resolved, dismissed | none | Restrict to one anomaly status. Omitting it returns active and resolved only, so ask for 'dismissed' explicitly to see the ones marked not real. |
list-commitments
Purpose: The individual Reserved Instances and Savings Plans, with their end dates.
Returns: each commitment with kind, service, start and end date, and status. Status is read from the end date, so a commitment whose term has passed reports as expired. The monthly commitment amount is present only where it is populated.
When to use: "What expires before the end of the quarter", or to size the cliff when a commitment lapses and its usage reverts to on-demand rates.
NOT for: Coverage and utilization percentages, use get-commitment-overview.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
kind | string | none | Restrict to one commitment kind, for example reserved_instance or savings_plan. |
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. Omit for every provider this organization has. Call get-identity if you are unsure which are available. |
status | string | none | Restrict to one status, for example active or expired. |
list-recommendations
Purpose: Savings opportunities LevelFour has found, largest first.
Returns: a paginated list with id, service, environment, account, monthly savings, the share of current spend that saves, status, risk level and a short description. Only open opportunities unless display_status says otherwise, and the filter applied is echoed back as filtered_to. Full detail, annual figures included, is deliberately omitted: open one with get-recommendation.
When to use: "What can we save." Filter server-side rather than paging and discarding: to answer "what can we save on RDS in staging", pass service and environment.
Examples: biggest open wins: no arguments. Everything actionable without a human: available_formats=["automated"]. What has already been applied: display_status=["optimized"]. What failed to apply: display_status=["failed"].
NOT for: Savings already realized, use get-realized-savings. These are potential and unbooked, and the two must never be added together.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
account | array of string | none | Restrict to these values. Omit for all. |
available_formats | array of string | none | Restrict to opportunities applicable this way: automated, iac, or manual. |
display_status | array of string | none | Restrict to these statuses. Omitted, only open opportunities are returned. The accepted values are 'available' (open, nobody has taken it), 'pending' (accepted and waiting to be applied), 'awaiting_approval', 'processing', 'optimized' (applied, and 'saved' is accepted for the same thing), 'failed' (an apply that did not complete), 'rejected' and 'unavailable' (locked by the plan). Any other value returns zero rows rather than an error. |
environment | array of string | none | Restrict to these values. Omit for all. |
page | integer, min 1 | 1 | 1-indexed page number. |
page_size | integer, 1 to 50 | 10 | Rows per page. |
provider | string | none | Cloud provider id, for example aws, gcp, azure or k8s. This tool reports on one provider at a time: omit it and the organization's first connected provider is used, which is not the whole bill for a multi-cloud organization. Call get-identity to see which are available. |
service | array of string | none | Restrict to these values. Omit for all. |
sort_by | monthly_savings, savings_percentage, recommendation_id, service, status, created_at | "monthly_savings" | Field to sort by. |
tag | array of string | none | Restrict to these values. Omit for all. |
update-anomaly-status
read-write key, and your client asks before calling it.Purpose: Record what the user decided about one detected cost anomaly.
Returns: the anomaly id and the status now recorded against it.
When to use: Only when the user has told you, in this conversation, what to do with a specific anomaly. "That spike was our migration" is planned. "That was not real" is dismissed. "We fixed it" is resolved.
NOT for: Changing any cloud spend. This writes a status on a LevelFour row and nothing else. Dismissing also trains the detector, so do not use it to tidy a list. To read anomalies rather than judge them, use list-anomalies. Read status in the result rather than assuming your argument was stored verbatim. 'acknowledged', 'planned' and 'resolved' all store resolved and differ in the feedback recorded, so the row will not read back as 'acknowledged'.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
anomaly_id | string | required | Anomaly id from list-anomalies. |
note | string | none | Short note recorded with the change. Only stored for status='resolved'. |
status | acknowledged, dismissed, resolved, planned | required | What to record. 'dismissed' says the spike was not a real anomaly and is negative feedback to the detector. The other three all close the anomaly and differ only in the feedback stored against it: 'acknowledged' confirms it was real, 'planned' says the spend was expected such as a migration or a load test, and 'resolved' closes it with no verdict and takes an optional note. Closing is one-shot, so a second call against an already closed anomaly is refused rather than applied. |