gasp-standard-mcp 1.14.2 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ Restart your MCP client after adding the config.
33
33
 
34
34
  | Tool | Description |
35
35
  |------|-------------|
36
- | `lookup_metric` | Full record for one metric: department, category, signal type, definition, formulas as written, the canonical formula with unit, forms, variants and breakdowns, benchmarks, validation rules, common mistakes, sources, aliases, the entities it measures, operating and market forms, the recommended sensitivity tier and access scope and the page URL. Matches exactly, ignoring case: formula id, graph node id, name with or without its abbreviation, abbreviation, graph label, alias or slug, with the full record preferred over a cross-reference stub. A name two departments define (Escalation Rate) lists each candidate with department, formula id and URL; prefix the department ("Support Escalation Rate") or pass the formula id. A glossary term returns its definition and a miss returns close names (misspellings included) |
36
+ | `lookup_metric` | Full record for one metric: department, category, signal type, definition, formulas as written, the canonical formula with unit, forms, variants and breakdowns, benchmarks, validation rules, common mistakes, sources, aliases, the entities it measures, operating and market forms, the recommended sensitivity tier and access scope and the page URL. Matches exactly, ignoring case: formula id, graph node id, name with or without its abbreviation, abbreviation, graph label, alias or slug, with the full record preferred over a cross-reference stub. A name two departments define (Activation Rate) lists each candidate with department, formula id and URL; prefix the department ("Product Activation Rate") or pass the formula id. A glossary term returns its definition and a miss returns close names (misspellings included) |
37
37
  | `lookup_definition` | Glossary term lookup (e.g. Signal Type, Sensitivity Tier, Access Scope, Metric Owner, CEL): full definition, section, canonical or reference link, where it is used and page URL. Matches the term, its slug or either part of "Term (Abbreviation)" exactly; a metric name points to `lookup_metric` and a miss returns close terms |
38
38
  | `list_metrics` | Metric names grouped by department and category, optionally filtered by department (case-insensitive, spaces or hyphens; an unknown name returns close names and the department list) and signal type (`outcome`, `leading`, `efficiency`, `operational`, primary or secondary). The Core tier is the Core department |
39
39
  | `get_formula` | Canonical formula in the GASP formula grammar, with unit, O/M forms, named variants, breakdowns (field, bucket and total mode), notes, input metrics with role and polarity, the fields and datasets the formula and each form read and the enum filters each form applies (CAC-O reads `cost_function in (gtm, implementation, onboarding)`, CAC-M `cost_function = gtm`), then the formula as written in the standard. Metrics that cannot be computed carry the reason |
@@ -50,21 +50,21 @@ The traversal tools (`infer_drivers`, `explain_path`, `coverage_gap`) return pat
50
50
 
51
51
  ## Data
52
52
 
53
- Version 1.14.2.
53
+ Version 1.16.0.
54
54
 
55
- - 301 metrics across 13 departments
56
- - 93 glossary definitions (80 SaaS domain + 13 GASP framework)
57
- - 95 relationship nodes (60 metrics + 28 entities + 7 concepts), 183 edges
55
+ - 306 metrics across 13 departments
56
+ - 94 glossary definitions (81 SaaS domain + 13 GASP framework)
57
+ - 100 relationship nodes (65 metrics + 28 entities + 7 concepts), 200 edges
58
58
  - 21 relationship types: metric signal (drives, reduces, predicts, component, measures), entity lifecycle (converts_to, parent_of, belongs_to), entity ownership (owns, grants, priced_by), entity interactions (manages, serves, engages_via, submits, represented_by, sources), entity financial flows (generates, produces, contributes_to, incurs)
59
- - 274 data requirements, one per metric with a formula, compiled from the formula at load time: fields with types, datasets, grain and a compiled example query
60
- - 274 query templates, one per metric with a formula, compiled by the same step. None of the SQL is hand-written. Every field placeholder names the dataset it is read from (`{{customer_status@customer}}`), so `generate_query` resolves per-dataset adapter mappings by tag, inside cohort subqueries too. The grammar's `window(expr, n unit)` gives Daily, Weekly and Monthly Active Users (DAU, WAU, MAU) their 1, 7 and 30 day windows. `all_time(expr)` lets retention numerators read past the period end. A multi-argument `count_distinct` counts distinct tuples (Breadth of Adoption counts account and feature pairs). A formula with a `breakdown` field compiles to one row per value of that field, with `total(expr)` reading the whole population for shares of total (Ticket Distribution by Type, CAC by Channel)
61
- - The test suite executes every compiled template against a seeded fixture in an in-process Postgres (PGlite) and checks ten metrics against independently computed values
59
+ - 279 data requirements, one per metric with a formula, compiled from the formula at load time: fields with types, datasets, grain and a compiled example query
60
+ - 279 query templates, one per metric with a formula, compiled by the same step. None of the SQL is hand-written. Every field placeholder names the dataset it is read from (`{{customer_status@customer}}`), so `generate_query` resolves per-dataset adapter mappings by tag, inside cohort subqueries too. The grammar's `window(expr, n unit)` gives Daily, Weekly and Monthly Active Users (DAU, WAU, MAU) their 1, 7 and 30 day windows. `all_time(expr)` lets retention numerators read past the period end. A multi-argument `count_distinct` counts distinct tuples (Breadth of Adoption counts account and feature pairs). A formula with a `breakdown` field compiles to one row per value of that field, with `total(expr)` reading the whole population for shares of total (Ticket Distribution by Type, CAC by Channel)
61
+ - The test suite executes every compiled template against a seeded fixture in an in-process Postgres (PGlite) and checks 25 metrics against independently computed values
62
62
  - Apache Ossie (incubating) export, written by `npm run derive` to `standard/exports/ossie/` and served at https://gaspwiki.com/downloads/ossie/: `gasp.ontology.yaml` (every dataset and GASP concept as an entity type, every field as a value type or relationship, enum values as constraints), `gasp.semantic_model.yaml` (every dataset with `OSSIE_SQL_2026` field expressions, the foreign-key relationships between them and every canonical formula as a metric, either a direct expression or staged through a per-period dataset that carries the compiled SQL; O/M forms and variants are separate metrics with the form in `custom_extensions`), `gasp.mapping.yaml` (the ontology mapped onto the model) and `coverage.json` (how each formula was exported and why). All three documents pass the upstream validator (vendored at `ossie-validator/`) and GASP's own cross-reference checks in `src/ossie/check.ts`, which cover references the upstream validator leaves alone
63
63
  - 28 business entities (Customer, Subscription, License, Lead, Opportunity, User, Ticket, Employee, Invoice, Partner, Project, Plan, Product, Campaign, Contact, Incident, Deployment, Release, Issue, Vulnerability, Feature, Requisition, Project Milestone, Deliverable, Quote, Order, Knowledge Article and Deal Registration). Every dataset with its own identity maps to an entity; ledger and period-level facts (cost events, financials, forecasts, web traffic) stay entity-free
64
64
  - 7 business concepts (Revenue, Bookings, COGS, Cohort, Pipeline, Segment and TCV)
65
- - 264 canonical fields across 51 datasets (event, snapshot and fact tables with date and key fields, entities, CEL class and activation rules). Enum fields declare their allowed values
66
- - 60 structured formulas with decomposed inputs (metric refs + field refs, with baseline inputs marked)
67
- - Canonical formulas for every metric in the GASP formula grammar (`standard/formulas.json`): 289 entries covering all 301 metrics (274 with formulas, 15 marked not computable with a reason, 2 department-qualified ids for shared headings), with units, O/M forms for ARR, NRR, CAC, LTV, LTV:CAC, CAC Payback and Gross Margin (a form can reference another metric's form, `metric.gross_margin.O`) and variants such as Adjusted NRR (served by `get_formula` and compiled by `generate_query` and `get_data_requirements`)
65
+ - 268 canonical fields across 52 datasets (event, snapshot and fact tables with date and key fields, entities, CEL class and activation rules). Enum fields declare their allowed values
66
+ - 65 structured formulas with decomposed inputs (metric refs + field refs, with baseline inputs marked)
67
+ - Canonical formulas for every metric in the GASP formula grammar (`standard/formulas.json`): 294 entries covering all 306 metrics (279 with formulas, 15 marked not computable with a reason, 2 department-qualified ids for shared headings), with units, O/M forms for ARR, NRR, CAC, LTV, LTV:CAC, CAC Payback and Gross Margin (a form can reference another metric's form, `metric.gross_margin.O`) and variants such as Adjusted NRR (served by `get_formula` and compiled by `generate_query` and `get_data_requirements`)
68
68
  - Access control classifications for every graph metric (sensitivity tier + access scope)
69
69
  - CEL `change_nature` field for revenue quality analysis (Adjusted NRR/GRR) and `segment` on revenue, contract and amendment events, so MRR, retention and churn metrics break down by customer segment
70
70
  - Per-department cross-references (NRR under Customer Success, CAC under Marketing) resolve to the full canonical record, with its access classification, structured formula and entity mappings
package/build/index.js CHANGED
@@ -129,7 +129,7 @@ function notAMetric(name, resolved) {
129
129
  return `No metric is named "${name}".${suggestMatches(data, name)}`;
130
130
  }
131
131
  // --- Tool 1: lookup_metric ---
132
- server.tool("lookup_metric", "The full record of one GASP Standard metric: department, category, signal type, page URL, definition, the formulas as written, the canonical GASP grammar formula (unit, forms, variants, breakdowns, fields and datasets), benchmarks, validation rules, common mistakes, sources, aliases, the entities it measures, operating and market forms, any adjusted variant and the recommended sensitivity tier and access scope. The name must match exactly, ignoring case: formula id, graph node id, full name, name without its abbreviation, abbreviation, graph label, alias or slug. A name several departments use (e.g. 'Escalation Rate') returns each candidate with department, formula id and URL; prefix the department ('Support Escalation Rate') or pass the formula id. A glossary term (e.g. 'Signal Type') returns its definition and a miss returns close names to retry with. Use get_formula for the formula alone and search when you do not know the name.", { name: z.string().describe("Exact metric name, abbreviation, alias, slug or formula id, case-insensitive (e.g. 'MRR', 'Net Revenue Retention', 'CAC Payback', 'support_escalation_rate'). Prefix the department to pick between metrics that share a name.") }, async ({ name }) => {
132
+ server.tool("lookup_metric", "The full record of one GASP Standard metric: department, category, signal type, page URL, definition, the formulas as written, the canonical GASP grammar formula (unit, forms, variants, breakdowns, fields and datasets), benchmarks, validation rules, common mistakes, sources, aliases, the entities it measures, operating and market forms, any adjusted variant and the recommended sensitivity tier and access scope. The name must match exactly, ignoring case: formula id, graph node id, full name, name without its abbreviation, abbreviation, graph label, alias or slug. A name several departments use (e.g. 'Activation Rate') returns each candidate with department, formula id and URL; prefix the department ('Product Activation Rate') or pass the formula id. A glossary term (e.g. 'Signal Type') returns its definition and a miss returns close names to retry with. Use get_formula for the formula alone and search when you do not know the name.", { name: z.string().describe("Exact metric name, abbreviation, alias, slug or formula id, case-insensitive (e.g. 'MRR', 'Net Revenue Retention', 'CAC Payback', 'product_activation_rate'). Prefix the department to pick between metrics that share a name.") }, async ({ name }) => {
133
133
  const resolved = resolveMetric(data, name);
134
134
  if (resolved.kind === "ambiguous") {
135
135
  return { content: [{ type: "text", text: formatAmbiguous(data, name, resolved.candidates) }] };
package/build/loader.d.ts CHANGED
@@ -73,13 +73,13 @@ export type Resolved = {
73
73
  };
74
74
  /**
75
75
  * Exact matches only, never a substring guess. Five tiers, first hit wins:
76
- * 1. the formula id or graph node id as typed ("support_escalation_rate",
76
+ * 1. the formula id or graph node id as typed ("product_activation_rate",
77
77
  * "nps"): unique keys, so never ambiguous;
78
78
  * 2. a name: the full name, the name without its abbreviation, the
79
79
  * abbreviation, or the label of the metric's graph node;
80
80
  * 3. an alias;
81
81
  * 4. the slug;
82
- * 5. the formula id with spaces for underscores ("Support Escalation Rate").
82
+ * 5. the formula id with spaces for underscores ("Product Activation Rate").
83
83
  * Non-stub metrics are tried through every tier first, then as a name
84
84
  * qualified by department; a cross-reference stub answers only when no full
85
85
  * record matches. Two full records matching in the same tier are ambiguous.
package/build/loader.js CHANGED
@@ -343,9 +343,11 @@ export function metricFormulaId(data, metric) {
343
343
  let bare = metric.slug.replace(/-/g, "_").replace(/^(\d+)_day_/, "day_$1_");
344
344
  if (/^\d/.test(bare))
345
345
  bare = `n_${bare}`;
346
- // Two departments can define different metrics under one heading (Activation
347
- // Rate, Escalation Rate). The metric that owns a graph node keeps the bare id;
348
- // the others are qualified by department so each formula has one meaning.
346
+ // A metric is qualified by department when its bare id would be shared: two
347
+ // departments defining metrics under one heading (no heading is shared today,
348
+ // the rule stays as a guard), or a bare id that is another metric's graph node
349
+ // (Product's "Activation Rate" against the Onboarding node activation_rate,
350
+ // hence product_activation_rate). The node owner keeps the bare id.
349
351
  const rivals = data.metrics.filter((m) => m !== metric && !isMetricStub(m) && m.slug === metric.slug);
350
352
  const nodeOwner = data.nodes.some((n) => n.id === bare) || rivals.some((m) => metricNodeId(data, m) === bare);
351
353
  if (rivals.length === 0 && !nodeOwner)
@@ -503,13 +505,13 @@ function namedBy(data, q) {
503
505
  }
504
506
  /**
505
507
  * Exact matches only, never a substring guess. Five tiers, first hit wins:
506
- * 1. the formula id or graph node id as typed ("support_escalation_rate",
508
+ * 1. the formula id or graph node id as typed ("product_activation_rate",
507
509
  * "nps"): unique keys, so never ambiguous;
508
510
  * 2. a name: the full name, the name without its abbreviation, the
509
511
  * abbreviation, or the label of the metric's graph node;
510
512
  * 3. an alias;
511
513
  * 4. the slug;
512
- * 5. the formula id with spaces for underscores ("Support Escalation Rate").
514
+ * 5. the formula id with spaces for underscores ("Product Activation Rate").
513
515
  * Non-stub metrics are tried through every tier first, then as a name
514
516
  * qualified by department; a cross-reference stub answers only when no full
515
517
  * record matches. Two full records matching in the same tier are ambiguous.
@@ -536,7 +538,7 @@ export function resolveMetric(data, name) {
536
538
  if (hits.length > 1)
537
539
  return { kind: "ambiguous", candidates: hits };
538
540
  }
539
- // A name qualified by its department: "Support Escalation Rate", "Escalation Rate (Support)"
541
+ // A name qualified by its department: "Product Activation Rate", "Activation Rate (Product)"
540
542
  for (const dept of new Set(full.map((m) => m.department))) {
541
543
  const d = dept.toLowerCase();
542
544
  const rest = q.startsWith(d + " ") ? q.slice(d.length + 1) : q.endsWith(` (${d})`) ? q.slice(0, -(d.length + 3)) : null;