Skip to main content
Glama

Costory: Your Finops MCP

query

Read-only

MINIMUM VALID CALL: { "queries": [{ "type": "cost", "name": "a", "metricId": "cost", "currency": "USD" }], "datePreset": "MTD", "aggBy": "Day" } Required per series: type (cost|metric|usage|formula|budget|externalMetric) and name. Put labels in alias.

Unified query tool for cost data, custom metrics, usage metrics, external (live integration) metrics, period comparisons, formulas, and budgets. QUERY NAMING: set type and name (prefer short ids like a/b/c for formulas); put human labels in alias (e.g. "Cost by environment") — never in name. Example: { type: "cost", name: "a", alias: "Cost by environment", groupBy: "cos_environment", ... }. For costs: metricId (cost column, default "cost") and currency (default "USD"). Use costMetricId and currency from get when aligning with a budget. For custom business metrics: use [{ type: "metric", metricId: "..." }] — get IDs from list_metrics. For infra usage metrics (e.g. CPU hours, network bytes): use [{ type: "usage", metricId: "..." }] — call suggest_usage_metrics first to discover valid metricIds for your scope. For live external metrics (not saved as Costory metrics): use [{ type: "externalMetric", provider: "...", integrationId: "...", metricName: "...", aggregator: "SUM", groupByFields: [], conditions: "..." }] — discover provider, integrationId, and metricName via list_metrics with includeExternal: true and a specific search term. Tsuga: metricName is the provider metric name; groupByFields are provider metric attributes; conditions is an optional provider filter string. Datadog: same shape as Tsuga — metricName is the Datadog metric name (e.g. system.cpu.user), groupByFields are tag keys (e.g. host, service), conditions is an optional Datadog tag filter (e.g. env:prod). When query is set it is the Datadog metrics query string (pass-through); metricName / aggregator / conditions / groupByFields are ignored; .rollup is required and the interval must be ≥ 24h (daily / weekly / monthly or seconds ≥ 86400). Costory will not fill an empty weekly series. CloudWatch: set provider: "cloudwatch"; metricName is Namespace/MetricName (e.g. AWS/EC2/CPUUtilization); groupByFields are CloudWatch dimension names (e.g. InstanceId); conditions is an optional dimension filter. BigQuery: set provider: "bigquery"; metricName is the fully-qualified table id (project.dataset.table); dateColumn, metricColumn, and gapFillingMethod are required — pick dateColumn/metricColumn from list_metrics schema (first DATE / first NUMERIC) and default gapFillingMethod to FORWARD_FILL; groupByFields are string column names (not CEL). S3: set provider: "s3"; identical field shape to bigquery — metricName is the fully-qualified table id returned by list_metrics (a Costory-managed external table over the customer's mirrored Parquet); same schema-derived columns. Use externalMetric for exploration when no saved metric matches; prefer saved { type: "metric" } when one exists. PERIOD: prefer datePreset (same DatePreset enum as dashboards/reports, e.g. MTD, LAST_MONTH, TRAILING_30_DAYS, LAST_3_MONTHS, YTD) over hand-computed from/to whenever a preset matches — mutually exclusive with from/to. Response includes the resolved period dates. For comparison: add compare: {} (or compare: { from, to }) — omit compare dates to auto-derive the preceding period (preset-aware, e.g. LAST_MONTH → previous calendar month). For formulas: add { type: "formula", formula: "a / b" } referencing other queries by name. For budgets: use [{ type: "budget", budgetId: "..." }] — despite the field name, this must be the budget version ID (same value as budgetVersionId from get); search returns the parent budget id only, so call get with that id to obtain budgetVersionId before querying. Optional chartType on each query: BAR, LINE, AREA, WATERFALL, or TABLE (defaults to LINE). groupBy is the SPLIT dimension, filterCel is the SCOPE (CEL). Before guessing CEL field names, call search with type: ["dimensions"] — empty query lists all fields; a keyword narrows to matching values. Costory label dimensions use a cos_ prefix (e.g. cos_service_name). Unlabelled resources have null on label dimensions; use filterCel with == null / != null (not is_null or string "null"). Custom virtual dimensions: use immutable bqName from list/get VDIM tools as groupBy / filterCel (not display name). Poll computeStatus until COMPLETED after publish. Optional analyze.changePoint (true or { ignoreWeekends }) runs change-point detection once per query after a timeseries result (incompatible with compare). Optional limit (integer 1–1000): max groups/rows per series. Do NOT set limit unless you need a different cap — when omitted, results default to 100 groups. Set limit above 100 (e.g. 250 or 500) when the user asks for a long tail or full breakdown list. OPTIONAL: After receiving results, consider calling "list_events" for the same date range to correlate cost changes with events, and "suggest_actions" to present follow-up options to the user. EXAMPLES: • "What are my total costs this month?" → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }], datePreset: "MTD", aggBy: "Day" } • "Break down AWS costs by service over the last 90 days" → { queries: [{ type: "cost", name: "a", alias: "AWS by service", metricId: "cost", currency: "USD", groupBy: "cos_service_name", filterCel: "cos_provider in ["AWS"]" }], datePreset: "TRAILING_90_DAYS", aggBy: "Week" } • "Show costs for resources without an environment label" → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD", filterCel: "cos_environment == null" }], datePreset: "TRAILING_30_DAYS", aggBy: "Day" } • "How did our costs change vs last month?" → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }], datePreset: "LAST_MONTH", compare: {} } • "Show CPU hours alongside compute costs" (call suggest_usage_metrics first to get valid metricIds) → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }, { type: "usage", name: "b", metricId: "k8s_cpu_hours" }], datePreset: "TRAILING_30_DAYS", aggBy: "Week" } • "What is our cost per request?" → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }, { type: "metric", name: "b", metricId: "" }, { type: "formula", name: "c", formula: "a / b" }], datePreset: "TRAILING_30_DAYS" } • "Cost per request volume" (after list_metrics with includeExternal: true and search: "request") → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }, { type: "externalMetric", name: "b", provider: "tsuga", integrationId: "", metricName: "", aggregator: "SUM" }, { type: "formula", name: "c", formula: "a / b" }], datePreset: "TRAILING_30_DAYS", aggBy: "Week" } • "Cost per BigQuery revenue table" (after list_metrics with includeExternal: true and search: "revenue") → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }, { type: "externalMetric", name: "b", provider: "bigquery", integrationId: "", metricName: "my-project.analytics.revenue", dateColumn: "event_date", metricColumn: "amount", gapFillingMethod: "ZERO", aggregator: "SUM" }, { type: "formula", name: "c", formula: "a / b" }], datePreset: "TRAILING_30_DAYS", aggBy: "Week" } • "Cost per CPU usage from Datadog" (after list_metrics with includeExternal: true and search: "cpu") → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }, { type: "externalMetric", name: "b", provider: "datadog", integrationId: "", metricName: "system.cpu.user", aggregator: "AVG", groupByFields: ["host"] }, { type: "formula", name: "c", formula: "a / b" }], datePreset: "TRAILING_30_DAYS", aggBy: "Week" } • "Cost per EC2 CPU from CloudWatch" (after list_metrics with includeExternal: true and search: "CPUUtilization") → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }, { type: "externalMetric", name: "b", provider: "cloudwatch", integrationId: "", metricName: "AWS/EC2/CPUUtilization", aggregator: "AVG", groupByFields: ["InstanceId"] }, { type: "formula", name: "c", formula: "a / b" }], datePreset: "TRAILING_30_DAYS", aggBy: "Week" } • "Budget per calendar month" → { queries: [{ type: "budget", name: "a", budgetId: "" }], datePreset: "LAST_3_MONTHS", aggBy: "Month" } (budgetVersionId from get, not the parent id from search) • "Budget month-to-date by day (cumulative within each month — which day did we reach the budget?)" → { queries: [{ type: "budget", name: "a", budgetId: "", rollingAggregation: { aggregator: "SUM", window: { preset: "MONTH" } } }], datePreset: "MTD", aggBy: "Day" } • "Formula: month-to-date cost vs month-to-date budget (both rolling SUM per month, e.g. utilization a/b)" → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD", rollingAggregation: { aggregator: "SUM", window: { preset: "MONTH" } } }, { type: "budget", name: "b", budgetId: "", rollingAggregation: { aggregator: "SUM", window: { preset: "MONTH" } } }, { type: "formula", name: "c", formula: "a / b" }], datePreset: "MTD", aggBy: "Day" } • Custom one-off range → { queries: [{ type: "cost", name: "a", metricId: "cost", currency: "USD" }], from: "2026-01-15", to: "2026-02-12", aggBy: "Day" }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
toNoExplicit window end (inclusive, YYYY-MM-DD). Use with from instead of datePreset.
fromNoExplicit window start (YYYY-MM-DD). Use with to instead of datePreset.
slugNoOrganization slug. Omit to auto-detect from your account (fails if you belong to multiple orgs).
aggByNoTime grain for the series: Hour, Day, Week, Month, or Period (default Month). Hour requires Costory Pro.Month
limitNoMax groups/rows per series. Omit to use the default (100). Increase up to 1000 when you need more than 100 breakdown groups.
analyzeNoOptional analyses. Set changePoint true (or { ignoreWeekends }) to run change-point detection once per query after a timeseries result. Incompatible with compare.
compareNoAdd a comparison period to show cost evolution side-by-side. Omit `from`/`to` to compare against the preceding period automatically; set `chartType` to choose how it renders.
queriesNoOne or more series (cost / metric / usage / externalMetric / formula / budget). Each needs a `name` for formulas (prefer short ids like a/b/c); put human labels in `alias`.
scopeIdNoOptional saved team scope id (from list_teams). Merges the scope whereClause into cost/usage queries.
datePresetNoOfficial date preset (same DatePreset as dashboards/reports, e.g. MTD, LAST_MONTH, TRAILING_30_DAYS). Prefer this over hand-computed from/to when a preset matches. Mutually exclusive with from/to.
forecastDaysNoExtend the query window by N days into the future and include forecasted values.

TDQS

A4.8/5.0
Behavior5/5

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

Beyond the readOnlyHint/destructiveHint annotations, the description discloses nuanced behavior: budgetId 'must be the budget version ID', 'Costory will not fill an empty weekly series', Datadog query strings are pass-through while metricName/aggregator/conditions are ignored, and compare auto-derives the preceding period. No statement contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with a minimum valid call, naming conventions, and organized examples, which helps scanning. However, it is very long, duplicates provider details already present in the input schema, and includes a tangential 'Poll computeStatus until COMPLETED after publish' line that does not clearly belong to this tool, so not every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity — 11 parameters, nested oneOf schemas, six query types, and five external providers — the description is remarkably complete. It covers required discovery calls, provider-specific field shapes, CEL null semantics, rolling aggregation, formulas, budgets, change-point analysis, limit defaults, and even follow-up tools like list_events and suggest_actions. The lack of an output schema is a minor gap because invocation guidance is thoroughly covered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Even though schema coverage is 100%, the description adds critical semantics not inferable from the schema: metricId for custom metrics comes from list_metrics and is not the datasourceId, usage metricIds come from suggest_usage_metrics, groupBy is the SPLIT dimension while filterCel is SCOPE, label dimensions use the cos_ prefix, and null matching requires == null. These additions materially improve correct invocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a concrete 'Unified query tool for cost data, custom metrics, usage metrics, external (live integration) metrics, period comparisons, formulas, and budgets' and provides a minimum valid call. This clearly identifies a specific verb, resource, and scope, and differentiates it from sibling tools like get, search, and suggest_actions.

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

Usage Guidelines5/5

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

The description gives explicit routing guidance: 'call suggest_usage_metrics first to discover valid metricIds', 'call search with type: ["dimensions"]' before guessing CEL fields, and 'call get with that id to obtain budgetVersionId' for budgets. It also states clear preferences such as 'prefer datePreset over hand-computed from/to' and 'prefer saved { type: "metric" } when one exists', making when-to-use and when-not-to-use unusually clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

TDQS

A4.1/5.0
Disambiguation4/5

Tools are organized by resource (alerts, dashboards, reports, events, virtual dimensions) with distinct actions, so most are clearly separable. The main confusion risks are the three report-delivery side-effect tools (run_report_now, retry_report_execution, transfer_report_execution) and the generic get that spans five resource types, though detailed descriptions mitigate these.

Naming Consistency4/5

The dominant verb_noun pattern (create_*, list_*, update_*, preview_*, get_*) is consistent and predictable across the set. Deviations like bare verbs query/search/get and the noun-only virtual_dimension_overlap_matrix are readable but break the otherwise uniform convention.

Tool Count3/5

44 tools is heavy and exceeds the comfortable range, but the server covers a genuinely broad FinOps platform spanning querying, dashboards, reports, alerts, events, virtual dimensions, docs, skills, and suggestions. Each tool has a distinct job, though the sheer count makes agent navigation harder.

Completeness3/5

Core workflows are well covered: query → dashboard/report/alert/event, plus a full virtual-dimension draft lifecycle. Notable gaps include alerts being create-only with no update/delete, no deletes for dashboards/events/published virtual dimensions, and budget management limited to query/get with no create/update.

Resources