Use leancosts with an agent
Point an AI agent at leancosts and let it answer from your bill instead of
guessing. leancosts runs a Model Context Protocol
server at POST /api/mcp. Every read the web app performs is also a tool, so an
agent reaches the same numbers the pages render.
1. Connect a client
Section titled “1. Connect a client”Claude Desktop, claude.ai, Cowork, mobile. Add a custom connector and approve it once: there is no token to copy. The full walkthrough, including what the grant permits and how to revoke it, is Connect Claude to leancosts.
https://api.leancosts.com/api/mcpClaude Code. It can send a header, so it uses a Personal Access Token (Use the API with a token) and skips OAuth:
claude mcp add --transport http leancosts \ https://api.leancosts.com/api/mcp \ --header "Authorization: Bearer leancosts_pat_xxxxxxxx"The leancosts plugin for Claude Code. It adds the server and two skills:
remediate takes one savings opportunity to executed, and connect takes one
cloud to its first sync. Export a Personal Access Token, then install it:
export LEANCOSTS_PAT=leancosts_pat_xxxxxxxx/plugin marketplace add intersector-io/leancosts/plugin install leancosts@leancostsCodex. Codex reads the same skills. Copy them into ~/.agents/skills/ and
add the server to ~/.codex/config.toml:
[mcp_servers.leancosts]url = "https://api.leancosts.com/api/mcp"bearer_token_env_var = "LEANCOSTS_PAT"Any other MCP client. The transport is stateless Streamable HTTP: one
JSON-RPC message per POST, application/json back. GET and DELETE answer
405, because there is no session to resume.
curl -s https://api.leancosts.com/api/mcp \ -H "Authorization: Bearer leancosts_pat_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'2. What the agent can do
Section titled “2. What the agent can do”Setup. get_onboarding_status says which clouds are connected and what is
still missing. get_grant_artifacts returns the read-only role or policy with
your organization’s values already substituted, for you to run with your own
cloud CLI. Azure connects through a device-code sign-in (start_azure_oauth),
AWS through a cross-account role (create_aws_connection). trigger_sync,
get_sync_progress and get_sync_logs cover the first ingest and why it
failed.
Costs. get_cost_summary is the canonical monthly total and its
breakdowns. get_cost_variance explains a movement between two months,
get_daily_costs splits one month by day, and get_cost_evolution gives the
month-over-month grid. get_cost_forecast returns the projection with its
disclosed band. query_costs runs an ad-hoc pivot, and export_costs hands
back the raw line items when the agent has to compute something leancosts does
not.
Findings and proof. list_opportunities is the Savings Register with its
one canonical open-claim total; get_opportunity adds the decision history
and, where the hunter produced one, the formula behind the claim.
list_hunter_findings and get_hunter_rollup read the last hunter snapshot.
list_ledger_entries is what the bill has actually graded, and
get_change_request is what your team did about a finding.
Triaging a finding. Every decision the register offers on the web has a
tool with the same permission: accept_opportunity, defer_opportunity (look
again on a date), discard_opportunity (not now; it comes back if the cost
changes), reject_opportunity (never), assign_opportunity,
set_opportunity_external_reference (your ticket id), defend_opportunity
(the figure finance agrees to, on an open row) and attribute_opportunity
(whether your team fixed a finding that stopped being detected). Each one
records the decision in leancosts and writes it to the audit trail, which
list_activity reads back.
Explaining a cost movement. list_variance_notes reads the notes people
left on a pair of months (a from and a to, both YYYY-MM), and
add_variance_note records one the human gives you. A note is plain text, tied
to that pair, and changes no cost, anomaly or forecast. Reading needs no
extra permission; adding one needs the same right as a note on a resource.
Acting on a finding. accept_opportunity, draft_change_request,
submit_change_request and approve_change_request take a register row
through the same steps as the web app, with the same permissions. Dual control
holds: the author cannot approve their own request unless your organization
turned self-approval on. get_change_request_execution_bundle hands back the
script, and mint_execution_receipt_token a token that can only report that
one change request as done. You run the script with your own credentials, and
it posts the receipt itself.
Tags and allocation. get_tag_coverage ranks the gaps by what each
untagged resource costs. get_tag_catalog is the taxonomy,
list_allocation_rules and list_shared_cost_patterns are how spend is
attributed to teams, and get_tag_reconciliation groups violations for bulk
fixing. list_staged_tags and list_virtual_tags show tags that exist inside
leancosts before, or instead of, reaching the cloud.
Saved queries. list_saved_queries names the cost questions your
organization has agreed on, and run_saved_query re-asks one by id or slug. A
saved query stores the question, never the figures, so it answers from today’s
data.
Talking to support. contact_support sends the leancosts support team a
bug report, a feature suggestion or a question, under your name. Support
replies to your email address. The agent should show you the message before it
sends it. It works during a trial too, and allows 10 messages per person per
hour.
3. Example prompts
Section titled “3. Example prompts”Setup:
- “Using leancosts, what’s connected and what’s still missing?”:
get_onboarding_status - “Give me the read-only AWS role for leancosts so I can run it myself.”:
get_grant_artifacts - “Connect our Azure tenant.”:
start_azure_oauth, thenget_azure_oauth_status - “Kick off a sync on the production subscription and tell me when it lands.”:
trigger_sync,get_sync_progress - “Why did the last sync fail?”:
get_sync_logs
Costs:
- “What did we spend last month, by service?”:
get_cost_summary - “Why did the bill move between June and July?”:
get_cost_variance - “Which day drove last month’s spike, and which resources?”:
get_daily_costs - “Show storage spend by team tag over the last six months.”:
get_cost_evolution,query_costs - “Are we going to blow the budget this month?”:
get_cost_forecast - “Pull the raw lines for June so I can reconcile them in a spreadsheet.”:
export_costs - “Which reservations are underutilised?”:
list_commitments - “The August Functions spike was a one-off backfill. Note that on August
against July.”:
add_variance_note
Findings and proof:
- “What are our open savings opportunities, biggest first?”:
list_opportunities - “Show me the arithmetic behind that idle-disk claim.”:
get_opportunity - “How much have we actually saved so far this year?”:
list_ledger_entries,get_impact_summary - “What is this VM, what does it cost, and is it covered by a reservation?”:
get_resource - “What does the idle-database hunter read, what does it claim, and when does
it stay quiet?”:
describe_hunter - “What change requests are waiting on approval?”:
get_change_request
Triaging a finding:
- “Push the idle-disk finding to next quarter.”:
defer_opportunity - “Dismiss that one for now: it is our DR replica.”:
discard_opportunity - “Give the database findings to ana@example.com and link ticket FIN-142.”:
assign_opportunity,set_opportunity_external_reference - “Who dismissed findings last month, and why?”:
list_activity
Acting on a finding (the remediate skill):
- “Fix the top leancosts opportunity.”:
list_opportunities,accept_opportunity,draft_change_request,submit_change_request,approve_change_request,get_change_request_execution_bundle,mint_execution_receipt_token
Tags and allocation:
- “How much untagged spend do we have, and where is it?”:
get_tag_coverage - “Which tag keys are we supposed to be using?”:
get_tag_catalog - “Break last month’s bill down by cost centre.”:
list_allocation_rules - “Stage the missing
env=prodtags on those resources.”:stage_tags - “Treat everything named
payments-*asteam=paymentsinside leancosts.”:upsert_virtual_tag - “Who is violating the tag policy right now?”:
get_tag_reconciliation
Saved queries:
- “What cost questions has the team already saved?”:
list_saved_queries - “Run the monthly-showback query.”:
run_saved_query - “Save that pivot as
platform-monthlyso finance can re-run it.”:save_query
Support:
- “Tell leancosts support the forecast chart is empty for last month. Show me
the message first.”:
contact_supportwithcategory: bug - “Suggest a feature to leancosts: a weekly digest in Slack.”:
contact_supportwithcategory: feature
4. The catalog
Section titled “4. The catalog”Every tool the endpoint serves, with its inputs and its gating. This section is generated from the server’s own registries, so it cannot disagree with what you can call.
Connect a cloud and watch the first sync. A locked trial can reach these, and the tools marked static, but nothing else.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
create_aws_connection | Create an AWS connection using the cross-account role from get_grant_artifacts (secretless: access keys are deliberately not accepted). | name (string, required); payerAccountId (string, required); payerRoleArn (string, required); externalId (string, required); memberRoleArnTemplate (string) | needs costs.connections.manage · reads connector setup state |
create_oci_connection | Create an OCI (Oracle Cloud) connection for one tenancy. | name (string, required); tenancyOcid (string, required); userOcid (string, required); homeRegion (string, required) | needs costs.connections.manage · needs the oci toggle · reads connector setup state |
create_salesforce_connection | Create a Salesforce connection for one org. | name (string, required); loginUrl (string, required); consumerKey (string, required); username (string, required) | needs costs.connections.manage · needs the salesforce toggle · reads connector setup state |
get_azure_oauth_status | Poll a One-Click Azure Connect flow started with start_azure_oauth. | flowId (string, required) | needs the azure toggle · reads connector setup state |
get_connection_permissions | Which documented read-only grants of one Azure, AWS or GCP connection are provably working, from evidence the last syncs already persisted (no cloud call). | provider (string, one of azure, aws, gcp, required); connectionId (string, required) | needs costs.connections.manage · reads connector setup state |
get_grant_artifacts | Machine-readable grant artifacts for connecting a cloud, with per-org values (AWS ExternalId) already substituted. | provider (string, one of azure, aws, gcp, required); agreement (string, one of standard, ea, mca, csp) | reads connector setup state |
get_onboarding_status | Where this organization stands in setup. | none | reads connector setup state |
get_sync_logs | Tail the persisted sync/validation log lines for one connection (the same stream the web Connection Console shows). | connectionId (string, required); since (integer, default 0) | reads connector setup state |
get_sync_progress | Live progress of running syncs for this organization (phase, counts, per-connection). | connectionId (string) | reads connector setup state |
probe_connection | Run a read-only probe of the account behind one OCI or Salesforce connection, with the connection’s stored credentials (same as the Run probe button). | provider (string, one of oci, salesforce, required); connectionId (string, required) | needs costs.connections.manage · reads ingested data (refused for a locked trial) |
request_admin_handoff | When the person you are helping cannot create Azure role assignments (Owner or User Access Administrator on the subscription), hand the connect step to whoever can. | adminEmail (string) | needs costs.connections.manage · reads connector setup state |
start_azure_oauth | Start the One-Click Azure Connect device-code flow: returns a verificationUri + userCode for the customer to sign in with. | tenantId (string, required); name (string, required) | needs costs.connections.manage · needs the azure toggle · reads connector setup state |
test_connection | Re-validate a connection against the provider right now (a real cloud API call, same as the Test button). | provider (string, one of azure, aws, gcp, oci, salesforce, required); connectionId (string, required) | needs costs.connections.manage · reads connector setup state |
trigger_sync | Start a sync for one connection (same as the Sync button). | provider (string, one of azure, aws, gcp, oci, salesforce, required); connectionId (string, required); force (boolean, default false) | needs costs.connections.manage · reads connector setup state |
What the bill is, where it moved, and what it is forecast to be.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
compare_periods | Compare two billing periods and explain the movement. | mode (string, one of variance, anomaly, default "variance"); month (string) | reads ingested data (refused for a locked trial) |
export_costs | The raw cost LINE ITEMS for one or more billing months, paged. | periods (array of string, required); format (string, one of json, focus, csv, default "json"); providers (array of string, one of azure, aws, gcp); accounts (array of string); services (array of string); tags (array of string); offset (integer, default 0); limit (integer, default 1000) | reads ingested data (refused for a locked trial) |
get_bedrock_usage | AWS Bedrock model usage for a day window, read from the AWS cost data the AWS connector already ingests. | from (string); to (string) | needs the bedrock toggle · reads ingested data (refused for a locked trial) |
get_cost_by_tag | The tags-by-month grid: every value of one tag key with its cost in each month of the range, its row total, the realized and planned savings credited to it, and its month-end projection (the same blend the /costs headline uses, over that row’s own months; available: false means no defendable forecast for that value, not zero, and the reason says which gate it failed). | tagKey (string); from (string); to (string); lens (string, one of live, planned, default "live"); providers (array of string, one of azure, aws, gcp); limit (integer, default 100) | reads ingested data (refused for a locked trial) |
get_cost_evolution | The month-over-month grid: every service (or every value of one tag key) with its amount in each ingested billing month, ranked by total. | dimension (string, one of service, tag, default "service"); tagKey (string); providers (array of string, one of azure, aws, gcp) | reads ingested data (refused for a locked trial) |
get_cost_forecast | This month’s projected spend with its DISCLOSED confidence band, next month’s model point, and the Azure budgets mirrored from the customer’s own subscriptions with how much of each is already consumed. | none | reads ingested data (refused for a locked trial) |
get_cost_meters | Drill one scope (an account, a service, or a single resource) down to its METER grain: the billed units under the money, with the top contributing resources. | accountId (string); serviceName (string); resourceId (string); period (string); topResources (integer, default 10) | reads ingested data (refused for a locked trial) |
get_cost_readiness | Whether the ingested data is COMPLETE enough to reason over, plus the baseline cohort the program measures against. | windowMonths (integer, default 12); providers (array of string, one of azure, aws, gcp) | reads ingested data (refused for a locked trial) |
get_cost_summary | The organization’s canonical spend summary. | lens (string, one of live, planned, default "live"); providers (array of string, one of azure, aws, gcp); region (string); cohortPeriod (string) | reads ingested data (refused for a locked trial) |
get_cost_variance | Explain the movement between two billing months. | mode (string, one of services, pvm, default "services"); from (string, required); to (string, required); accountId (string); providers (array of string, one of azure, aws, gcp) | reads ingested data (refused for a locked trial) |
get_daily_costs | Day-by-day spend inside one billing month, each day with its top services. | period (string); date (string); topResources (integer); providers (array of string, one of azure, aws, gcp) | reads ingested data (refused for a locked trial) |
get_dynatrace_usage | Dynatrace observability spend for a day window. | from (string); to (string); limit (integer, default 50) | needs the dynatrace toggle · reads ingested data (refused for a locked trial) |
get_finops_scorecard | How well the org is running FinOps, from three angles. | view (string, one of resources, maturity, teams, default "resources"); status (string, one of red, yellow, green); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
get_impact_summary | The realized-savings ledger rolled up: what the org’s executed changes actually saved, measured against the bill, net of the tool’s own subscription cost. | from (string); to (string) | reads ingested data (refused for a locked trial) |
get_kubernetes_costs | Cost allocated per Kubernetes namespace, per cluster, for the current and previous billing month - AKS, EKS and GKE. | months (integer, default 2) | reads ingested data (refused for a locked trial) |
get_savings_pipeline | The savings funnel (identified → in-flight → executed → realized) plus the counterfactual: what the bill would have been had none of the executed work happened. | windowMonths (integer, default 12) | reads ingested data (refused for a locked trial) |
get_tag_drivers | Explain ONE tag value’s month-over-month swing: which resources and services under that tag drove the change against the prior calendar month. | tagKey (string, required); tagValue (string); period (string, required) | reads ingested data (refused for a locked trial) |
list_alert_rules | The org’s alerting configuration and history. | include (string, one of rules, channels, events, default "rules") | reads ingested data (refused for a locked trial) |
list_anomalies | Detected cost anomalies with their totals and classification split. | status (string, one of open, acknowledged, snoozed, withdrawn); direction (string, one of increase, decrease); classification (string); includeSuppressed (boolean, default false) | reads ingested data (refused for a locked trial) |
list_change_requests | Recent change requests across every status (draft / approved / rejected / executed) with author, estimated monthly impact and last update. | none | reads ingested data (refused for a locked trial) |
list_commitments | Active reservations and savings plans with utilization %, covered dollars and monthly waste, worst first. | providers (array of string, one of azure, aws, gcp) | reads ingested data (refused for a locked trial) |
list_connector_suggestions | Vendors the cloud bill already charges for that the org has not connected: Azure DevOps and Databricks billed through the Azure invoice. | limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_insights | The org’s open optimization findings. | view (string, one of by_category, ranked, default "by_category") | reads ingested data (refused for a locked trial) |
list_resources | The org’s cloud resource inventory grouped by provider, with each resource’s tags. | providers (array of string, one of azure, aws, gcp) | reads ingested data (refused for a locked trial) |
query_costs | Aggregate this organization’s ingested cost lines. | groupBy (string, one of service, account, period, provider, resource, tag, default "service"); tagKey (string); period (string, one of last_month, last_3_months, last_6_months, last_12_months, all_time, explicit, default "last_3_months"); periods (array of string); providers (array of string, one of azure, aws, gcp); services (array of string); accounts (array of string); tagFilters (array of string); tagExcludes (array of string) | reads ingested data (refused for a locked trial) |
Marketplace
Section titled “Marketplace”The products bought through AWS Marketplace and Google Cloud Marketplace: spend, seller, the contract term and renewal behind a paid-up-front fee, and the connector that explains each product. A contract is shown only when its agreement matches a payment to the cent, and the read says when the agreements could not be read.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
list_marketplace_products | Every product the org buys through a cloud marketplace (AWS Marketplace, Google Cloud Marketplace and Azure Marketplace) over the last 13 months, largest first. | provider (string, one of aws, gcp, azure); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
Databricks
Section titled “Databricks”DBU consumption and its list cost, read from the Databricks account’s own system tables. List price is not an invoice, and for an Azure workspace the invoice is on the Azure bill beside it.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
get_databricks_usage | Databricks DBU usage and list cost for the organization’s connected workspaces over the last 30, 60 or 90 days. | days (string, one of 30, 60, 90, default "30") | needs the databricks toggle · reads ingested data (refused for a locked trial) |
list_databricks_compute | The Databricks compute inventory the last sync captured. | kind (string, one of cluster, warehouse, pool); workspaceId (string); limit (integer, default 50) | needs the databricks toggle · reads ingested data (refused for a locked trial) |
Findings and proof
Section titled “Findings and proof”The savings register, the hunters behind it, the change requests, and the ledger that grades them against the bill.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
approve_change_request | Approve a SUBMITTED change request. | id (string, required) | reads ingested data (refused for a locked trial) |
create_change_request | File a change request for something YOU found - a check leancosts does not ship as a hunter. | title (string, required); why (string, required); scope (object); changes (array of object); links (array of string); opportunityId (string); submitImmediately (boolean, default false); detectionPattern (object); estimatedMonthlyImpactUsd (number) | needs change_request.create · reads ingested data (refused for a locked trial) |
describe_connector | What connecting a provider buys, WITHOUT a connection. | provider (string, one of azure, aws, gcp, oci, databricks, openai, anthropic, github, dynatrace, azure_devops, salesforce) | static (reads none of your data) |
describe_hunter | How a hunter works, WITHOUT running it. | source (string); kind (string) | static (reads none of your data) |
draft_change_request | Turn an accepted Savings Register row into a DRAFT change request. | opportunityId (string, required); targetSku (string) | needs change_request.create · reads ingested data (refused for a locked trial) |
get_change_request | One change request: what it proposes, who authored it, its approval status, and its SIGNED estimated monthly bill delta: NEGATIVE means the bill goes down. | id (string, required); include (array of string, one of projection, sub_items) | reads ingested data (refused for a locked trial) |
get_change_request_execution_bundle | The runnable artifact for an APPROVED change request: the script, its provider and its rollback, packaged for the customer to schedule and run with THEIR OWN credentials. | id (string, required) | needs change_request.execute · reads ingested data (refused for a locked trial) |
get_commitment_recommendation | The spend-ranked resource list behind ONE commitment (reservation or savings plan) recommendation. | id (string, required) | reads ingested data (refused for a locked trial) |
get_hunter_coverage | Which cloud services the hunters looked at for THIS organization in its last sweep, one row per service. | provider (string, one of azure, aws, gcp, oci, databricks, openai, anthropic, github, dynatrace, azure_devops, salesforce); state (string, one of not_connected, deferred, not_run, errored, blocked, finding, quiet); limit (integer, default 500) | reads ingested data (refused for a locked trial) |
get_hunter_readiness | Whether the hunters can run yet: connections, discovery, cost completeness and metrics as an ordered stepper with the blocking gap named at each rung. | none | reads ingested data (refused for a locked trial) |
get_hunter_rollup | The hunter fleet’s top line, from the same snapshot list_hunter_findings reads. | view (string, one of categories, impact, families, snapshot_status, default "categories") | reads ingested data (refused for a locked trial) |
get_ledger_entry | One impact-ledger entry in full, including its evidence window and the scope the measurement was taken over. | id (string, required) | reads ingested data (refused for a locked trial) |
get_opportunity | One Savings Register row in full. | id (string, required) | reads ingested data (refused for a locked trial) |
get_resource | One resource in depth. | externalId (string, required); include (array of string, one of details, meters, metrics, sizing, links, daily); days (integer, default 90) | reads ingested data (refused for a locked trial) |
list_accounts | The billing containers the organization has connected: Azure subscriptions, AWS accounts, GCP projects, with their provider and name. | limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_activity | The organization’s audit trail, newest first. | category (string, one of authentication, access, tokens, connections, change_requests, savings, tagging, sso, automation, notifications, admin, other); actor (string); since (string); until (string); cursor (string); limit (integer, default 50) | needs governance.audit.read · reads ingested data (refused for a locked trial) |
list_hunter_findings | The hunters’ current findings, read from the persisted snapshot, never a live sweep. | category (string, one of compute, database, storage, network, commitments, observability-ai, integration); source (string); cloud (string, one of azure, aws, gcp, ai_workload); resourceExternalId (string); includeLowConfidence (boolean, default false); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_ledger_entries | The impact ledger row by row: every claimed saving with the change it came from and, once graded against the bill, its MEASURED amount. | from (string); to (string); status (string, one of draft, posted, disputed, void); entryType (string); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_opportunities | The Savings Register: every finding the org has triaged, with its claim and its lifecycle status. | view (string, one of register, value_history, value_events, default "register"); cloud (string, one of all, azure, aws, gcp, default "all"); status (array of string, one of surfaced, accepted, deferred, discarded, permanently_rejected, in_progress, done, realized, auto_resolved); source (array of string, one of hunter, commitment, anomaly); direction (array of string, one of down, up); confidence (array of string, one of low, medium, high); claimMin (number); claimMax (number); kind (string); assignee (string); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_resource_metrics | The persisted utilization windows for MANY resources at once - the bulk twin of get_resource with include=metrics, and the way to check one signal across an estate without paying a call per resource. | externalIds (array of string, required); metrics (array of string); windowDays (integer) | reads ingested data (refused for a locked trial) |
list_spend_commitments | The aggregate spend obligations above every reservation and savings plan (an Azure MACC, an AWS EDP or PPA, a GCP contract), each with its burn-down. | limit (integer, default 50) | reads ingested data (refused for a locked trial) |
mint_execution_receipt_token | Mint the report-back token for an APPROVED change request: a signed credential scoped to this ONE change request, single purpose, expiring, and revocable from the web. | id (string, required) | needs change_request.execute · reads ingested data (refused for a locked trial) |
record_change_request_execution | Record what actually happened to an APPROVED change request. | changeRequestId (string, required); executedAt (string, required); outcome (string, one of as_proposed, modified, not_applied, required); note (string); executedChanges (array of object) | needs change_request.execute · reads ingested data (refused for a locked trial) |
search_resources | Find resources across the estate by provider, type, resource group, account, tag or free text, ranked by their latest CLOSED period’s attributed spend. | nameContains (string); providers (array of string, one of azure, aws, gcp); resourceTypes (array of string); resourceGroups (array of string); accountIds (array of string); tagKey (string); tagValue (string); tagMissingKey (string); minMonthlyCost (number); maxMonthlyCost (number); lens (string, one of live, planned, default "live"); pageNumber (integer, default 1); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
submit_change_request | Submit a DRAFT change request for approval (draft → submitted). | id (string, required) | reads ingested data (refused for a locked trial) |
Register triage
Section titled “Register triage”Record the team’s decision on a register row, one tool per decision the web drawer offers: accept, defer, dismiss for now or for good, answer who resolved it, assign an owner, set the defended amount, attach a ticket id. Each re-checks the capability its web action needs, and nothing touches the cloud.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
accept_opportunity | Accept a Savings Register row: surfaced or deferred → accepted, the triage decision that must come before draft_change_request. | opportunityId (string, required) | needs register.triage · reads ingested data (refused for a locked trial) |
assign_opportunity | Set who owns a register row: an organization member’s email, or an empty string to unassign. | opportunityId (string, required); assignee (string, required) | needs register.triage · reads ingested data (refused for a locked trial) |
attribute_opportunity | Answer “this opportunity stopped being detected: did your team fix it?” for an auto-resolved row whose attribution is pending. | opportunityId (string, required); answer (string, one of applied, independent, required) | needs register.triage · reads ingested data (refused for a locked trial) |
defend_opportunity | Set the defended amount of an OPEN register row. | opportunityId (string, required); defendedAmountUsd (number, required); reason (string, required) | needs register.defend · reads ingested data (refused for a locked trial) |
defer_opportunity | Defer a surfaced or deferred register row until a review date in the future (“look at this again later”). | opportunityId (string, required); reviewDate (string, required) | needs register.triage · reads ingested data (refused for a locked trial) |
discard_opportunity | Dismiss a register row FOR NOW (“not now”), with the human’s reason. | opportunityId (string, required); reason (string, required) | needs register.triage · reads ingested data (refused for a locked trial) |
reject_opportunity | Reject a register row PERMANENTLY (“won’t fix”), with the human’s reason. | opportunityId (string, required); reason (string, required) | needs register.triage · reads ingested data (refused for a locked trial) |
set_opportunity_external_reference | Record the customer’s own ticket id (Jira, ServiceNow) on a register row, or an empty string to clear it. | opportunityId (string, required); externalReference (string, required) | needs register.triage · reads ingested data (refused for a locked trial) |
Tags and allocation
Section titled “Tags and allocation”Tag coverage, the taxonomy, allocation rules, the org units above allocation owners with their month rollup, the drivers a shared cost is split by, and tags staged or declared inside leancosts.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
get_allocation_proposal | The latest PERSISTED allocation-rule proposal the product generated for this org. | none | reads ingested data (refused for a locked trial) |
get_org_unit_rollup | One month of allocated cost rolled up the organization’s units, with the month before. | period (string); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
get_org_unit_tree | The organization’s hierarchy above the allocation owners (presidency, directorates, areas), depth first. | limit (integer, default 50) | reads ingested data (refused for a locked trial) |
get_tag_catalog | The org’s tag taxonomy and the machinery around it. | include (array of string, one of definitions, values, vocabulary, normalization, system_tags, tenant_pattern); sourceKey (string); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
get_tag_coverage | How well the estate is tagged. | view (string, one of required, by_key, default "required"); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
get_tag_reconciliation | Legacy tag debt: every resource violating the required taxonomy, grouped for bulk remediation. | view (string, one of scan, runs, default "scan"); groupingMode (string, one of resource_group, account, service, with_linked, default "resource_group"); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
link_org_unit_owner | Put an allocation owner (a recordTypeKey + recordKey target from list_allocation_rules include=summary, such as team = checkout) in an org unit. | unitId (string, required); recordTypeKey (string, required); recordKey (string, required) | needs costs.allocation.edit · reads ingested data (refused for a locked trial) |
list_allocation_drivers | The quantity per owner per month that a shared-cost pool is split by. | driverKey (string); period (string); origin (string, one of cost, tag, usage, pushed); limit (integer, default 50); offset (integer) | reads ingested data (refused for a locked trial) |
list_allocation_rules | How shared and tagged spend is attributed to teams and cost centres. | include (string, one of rules, summary, both, trend, default "both"); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_shared_cost_patterns | The rules that spread a shared cost pool (platform, networking, observability) across tag-value targets, each with a PREVIEW of what it would allocate in the latest period. | limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_staged_tags | The Tag Studio staging layer: tag changes recorded in leancosts and NOT applied to the customer’s cloud. | status (string, one of staged, applied); provider (string, one of azure, aws, gcp, databricks, openai, anthropic, github, dynatrace, external); withCli (boolean, default false); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
list_virtual_tags | Every virtual-tag rule in the org, with the tag it writes, the filter that selects resources, how many resources it currently matches (matchedResources) and how many it skipped because the customer’s own cloud tags them production (skippedProduction). | none | reads ingested data (refused for a locked trial) |
push_allocation_driver | Replace the whole month of one pushed driver: the rows sent are the month, and the rows it held before go. | driverKey (string, required); period (string, required); unit (string, required); rows (array of object, required) | needs costs.allocation.edit · reads ingested data (refused for a locked trial) |
stage_tags | Stage one tag change across a set of resources. | resources (array of string, required); tagKey (string, required); op (string, one of set, unset, default "set"); value (string) | needs tag.staging.write · reads ingested data (refused for a locked trial) |
suggest_allocation_drivers | Which driver could split a shared-cost pool in one month, with the split each would give. | patternId (string, required); period (string, required) | reads ingested data (refused for a locked trial) |
upsert_org_unit | Create an org unit, or change one when id is given (only the fields you pass change). | id (string); name (string); parentId (string); levelLabel (string); owner (string); accountingCode (string) | needs costs.allocation.edit · reads ingested data (refused for a locked trial) |
upsert_virtual_tag | Create or update one virtual-tag rule: a tagKey=tagValue written onto every resource the filter selects. | id (string); name (string, required); tagKey (string, required); tagValue (string, required); providers (array of string, one of azure, aws, gcp, databricks, openai, anthropic, github, dynatrace, external); resourceTypes (array of string); tagFilters (array of string); accountIds (array of string); resourceGroups (array of string); missingTagKeys (array of string); nameContains (string) | needs tag.staging.write · reads ingested data (refused for a locked trial) |
The context people left on a resource (who owns it, why it looks like this, what not to touch) and on a pair of billing months (why the bill moved). A note is inert - it never changes a finding, a cost or a tag - and it is worth reading before recommending an action or explaining a variance.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
add_resource_note | Append one operational note to a resource, attributed to the caller and timestamped. | externalId (string, required); body (string, required) | needs costs.notes.write · reads ingested data (refused for a locked trial) |
add_variance_note | Append one note to a from/to pair of billing months, attributed to the caller and timestamped. | from (string, required); to (string, required); body (string, required) | needs costs.notes.write · reads ingested data (refused for a locked trial) |
list_resource_notes | Operational notes humans and agents left on one resource (who owns it, why it looks like this, what not to touch), newest first with author and timestamp. | externalId (string, required); limit (integer, default 100) | reads ingested data (refused for a locked trial) |
list_variance_notes | Plain-language notes humans and agents left on one from/to pair of billing months (why the bill moved), newest first with author and timestamp. | from (string, required); to (string, required); limit (integer, default 100) | reads ingested data (refused for a locked trial) |
Service retirements
Section titled “Service retirements”Features a vendor has announced it will retire on a date, and which of the organization’s resources they touch. A retirement is a dated fact, never a saving: it carries no money.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
list_retirements | Vendor-announced service retirements that touch the org’s resources. | resource (string); withinDays (integer); limit (integer, default 100) | reads ingested data (refused for a locked trial) |
Requirements
Section titled “Requirements”What the organization has declared its workloads need. A requirement is the customer’s own word, and it is the only thing that unlocks the findings that remove redundancy or shorten backup retention on production.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
list_requirements | Every active requirement this organization has declared. | serviceKind (string, one of managed_database, virtual_machine, service_plan, node_pool, storage, cache, search, messaging, data_platform); scope (string, one of org_default, account, class, resource); limit (integer, default 50) | reads ingested data (refused for a locked trial) |
set_requirements | Declare requirements for this organization, 1-100 rows in ONE transaction (all rows land or none do). | rows (array of object, required) | needs governance.admin · reads ingested data (refused for a locked trial) |
Program settings
Section titled “Program settings”The savings appetite (how much evidence a finding needs before it reaches the register) and the monthly budget. Each write carries the value it replaces, so a stale one is refused instead of undoing someone else’s change.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
get_program_tuning | The organization’s savings appetite and monthly budget, as they are in effect now. | none | needs governance.admin · reads ingested data (refused for a locked trial) |
set_monthly_budget | Set the organization’s monthly budget in USD, 0-100000000. | monthlyBudgetUsd (number, required); expected (number, required) | needs governance.admin · reads ingested data (refused for a locked trial) |
set_savings_appetite | Set the organization’s savings appetite. | appetite (string, one of conservative, balanced, aggressive, required); expected (string, one of conservative, balanced, aggressive, required) | needs governance.admin · reads ingested data (refused for a locked trial) |
Alert rules
Section titled “Alert rules”Tune an existing alert rule: its threshold, the channels it delivers to, or whether it is active. Reading rules, channels and events is list_alert_rules (under Cost analysis). A threshold write carries the values you read, so a stale one is refused, and the rule’s name, metric and scope never change.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
update_alert_rule | Tune an existing alert rule: its threshold, the channels it delivers to, or whether it is active. | ruleId (string, required); threshold (object); expected (object); channelIds (array of string); active (boolean) | needs governance.admin · reads ingested data (refused for a locked trial) |
Saved queries
Section titled “Saved queries”Cost questions the organization has named, so the same question is asked the same way twice.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
list_saved_queries | The cost queries this organization has named and shared. | none | reads ingested data (refused for a locked trial) |
run_saved_query | Execute one saved query by its id or slug and return the rows, the plain-language summary and the audit trace that defends them. | idOrSlug (string, required) | reads ingested data (refused for a locked trial) |
save_query | Name a cost query so the whole organization can re-run it from the Copilot page or from run_saved_query. | name (string, required); groupBy (string, one of service, account, period, provider, resource, tag, default "service"); tagKey (string); period (string, one of last_month, last_3_months, last_6_months, last_12_months, all_time, explicit, default "last_3_months"); periods (array of string); providers (array of string, one of azure, aws, gcp); services (array of string); accounts (array of string); tagFilters (array of string); tagExcludes (array of string) | needs change_request.create · reads ingested data (refused for a locked trial) |
Support
Section titled “Support”Report a problem, suggest a feature or ask a question. The message is emailed to the leancosts support team under the person’s name, and they reply by email. A locked trial can reach it.
| Tool | What it answers | Inputs | Notes |
|---|---|---|---|
contact_support | Send a message to the leancosts support team: report a problem (bug), suggest a feature (feature), or ask a question about the product or billing. | category (string, one of question, bug, billing, feature, other, required); message (string, required); recommendationRef (string) | static (reads none of your data) |
5. Rules the server enforces
Section titled “5. Rules the server enforces”- Read-only by construction. leancosts holds no write credential for your cloud. The tools that write, write inside leancosts: tags staged or virtual, notes, saved queries, requirements, and a change request’s steps (accept, draft, submit, approve, mint its receipt token, record its execution). Each requires the same permission the matching page does. A change reaches your cloud only when you run its script with your own credentials.
- Typed inputs only. No tool takes a free-text question. The agent picks a pivot, a window and filters; leancosts executes and returns the rows with the audit trace that defends them.
- Canonical USD. Cost figures come back in canonical USD, so clouds compare. The two exceptions say so in their own output: Azure budgets and Azure Kubernetes namespace rows stay in the currency Azure reports them in, and must not be added to a USD total.
- Null is not zero. A figure never observed comes back null and renders as
an em dash. Reporting it as
$0would be a claim the data does not support. - Trial gate. An unsubscribed trial outside its free-scan window can reach the setup tools and nothing else: exactly the surface it reaches in the web app.
- Metered. Every
tools/callcounts against your plan’s monthly MCP call allowance; past it the server answers429with the number and the reset date.initialize,tools/listandpingare free, so a client can always connect and read the catalog. Your plan and its limits are in Settings → Subscription. - Secretless. No tool accepts a client secret, access key or service-account key.
Verify
Section titled “Verify”Ask the client to list its leancosts tools, or run the tools/list curl above.
A healthy connection answers with the tool array.
| Response | Cause |
|---|---|
401 | The token is missing, expired or revoked. |
403 oauth_wrong_audience | A connector token is being sent somewhere other than POST /api/mcp. |
429 | This month’s MCP call allowance is spent; it resets on the 1st. |
| A tool refuses with a trial message | The workspace is an unsubscribed trial outside its free-scan window. |