Skip to main content
Glama

Apply model patch

layerz_patch
Destructive

Mutate a model with batched ops (create/replace/update/delete/move/list_create/list_update/list_delete/meta). ops must be JSON objects with a string op field, never JSON-encoded strings. The batch is all-or-nothing: if one op fails validation or a replace migration is incompatible, nothing is persisted. Give a create op a temp id and reference it as parent/source/target/series on later ops in the same batch (wire references before the real UID exists). Temp ids also resolve inside FORMULAS ($_my_id * 1.2), matching whole tokens only, so a full dependency chain ships in one batch — including a SELF-reference: a create may reference its own temp id (or its own name) in its formula, e.g. {op:"create",id:"_y",name:"Annee",formula:"IF($_y_Y-1 = 0, $start, $_y_Y-1 + 1)"} — no dummy-formula + update round-trip. To swap a referenced item atomically, prefer replace over create+rewire+delete. Hierarchy rules: only section and dashboard live at root; section accepts every role except chart/kpi/mini_table AND can be nested inside another section to model true sub-sections (use this for structural grouping with no values); dashboard accepts ONLY chart/kpi/mini_table; balance and formula-with-no-expression act as aggregators that sum their children (use a no-expression formula when you need an aggregate VALUE — e.g. "Total Revenue" — and use a nested section when you just need structural grouping with no computed value); a formula is an aggregator XOR a transformation — giving it BOTH an expression and children is rejected (FORMULA_EXPR_AND_CHILDREN); assumption and callup are leaves. Callup role: a callup is a typed mirror of ONE source whose value flows INTO its parent container — use it for balance flows (a flow attached as a callup child of a balance) and for surfacing a value inside a section/subtotal. Set source to the source item's UID (or a temp id from the same batch). The callup inherits its source's display_name and timeline_ref — do not set timeline_ref on a callup. Do NOT use a callup for arithmetic: inside a real formula, reference the source directly (e.g. Revenue * 1.2) rather than mirroring it through a callup first. On create/replace, a formula whose expression is a single bare reference with no timeline_ref override is auto-posed as a callup mirror, so a plain pass-through like formula:"Revenue" becomes callup{source:Revenue}. Stock-flow modeling (rolling balances — cash, ARR, retained earnings, debt, fixed assets): (1) create a balance with opening_balance: X; (2) for each flow, create a formula whose value is the per-period delta — outflows must return negative values; (3) attach each flow as a callup child of the balance with source: <flow_uid>; (4) if a flow depends on the balance itself (interest on debt, churn on ARR), use the lag suffix _M-1 / _Q-1 / _Y-1 on the balance UID to read the previous-period value and break the cycle. A balance without callup children stays flat at opening_balance forever — that is almost always a modeling bug. List mode (per-element series): three coexisting concepts must not be confused — (a) "Dataset entity" (plan/actual/forecast, see layerz_import_branch) is a stacked input version of the model; (b) the "Lists registry" (list_create/list_update/list_delete) is a named registry of elements (Functions, Products, Channels, Debt Tranches, Employees…); (c) "list-mode items" are assumption/formula/balance items that carry liste_ref and expand to one virtual instance per element. The pre-MDK 0.2.0 layer role is gone. Pattern: (1) {op:"list_create",id:"_functions",name:"Functions",items:[{id:"_eng",label:"Engineering"},{id:"_sales",label:"Sales"}]} — list_create is idempotent by name; both the list itself and each item can carry an id (temp_id) usable from later ops in the same batch. (2) Bind on the consumer with liste_ref:"_functions" (temp id), display_name, or the returned UID. (3) Populate per-element data via timeline_values keyed by element UID, label, or item temp_id: value = null | number | (number|null)[]; a scalar is auto-wrapped into a 1-element array. Passing scalar value/values on a list-mode item is rejected — those fields are silently dropped at compute time. Balance-list rule: when a balance carries liste_ref, every list-typed child MUST share the same liste_ref (mismatch → validator-outputs.ts: "Children of a list-mode balance must share the same list or be scalar"); scalar children broadcast across all elements. Cross-list bridging is declared on the list element itself via list_update with items:[{label:"Direct",mapped_to:{Products:["Pro","Enterprise"]}}] (channel Direct rolls up products Pro and Enterprise). Values — two regimes, keyed on liste_ref: (a) LIST-MODE item → timeline_values keyed by element UID/label/temp-id IS the normal write path (see List mode above); value/values are rejected. (b) NON-LIST item → type a plan/forecast with value (scalar, broadcast to every period) or values (array at the item’s grain); the patch surface never writes timeline_values there, nor on a computed line (formula/balance) — imported actuals/overrides are import-owned (use layerz_import_branch or the Sources import). A one-cell edit uses a period-keyed map {"2026-06":[val]} (also "2026" / "2026-Q1"): it merges into the typed values series, leaving the other periods intact. Formula language: operators + - * / ^ ( ) (^ = power, Excel semantics: left-associative, binds tighter than *) and comparisons > < >= <= = !=. Scalar functions: IF, AND, OR, NOT, MAX, MIN, ABS, POW, POWER, EDATE, YEAR, MONTH, IN_PERIOD, NPV, IRR, PMT, IPMT, PPMT. IRR/NPV consume a FULL series and must be the item's ENTIRE formula, their series argument a single item reference — IRR($fcfe), NPV($rate, $fcfe); nesting them in a larger expression or passing an expression as the series is rejected (SCALAR_FUNCTION_MISUSE); the result is a single timeless value (timeline constant). IRR($fcfe, CUMULATIVE) is the temporal variant: one IRR per period over the series from the start through that period (the project-finance run-up line; 0 before convergence). Per-period tokens: PERIOD_YEAR (calendar year of the evaluated period — date-gate lines with IF(PERIOD_YEAR >= commissioning_year, ...), no hand-rolled year counter needed) and PERIOD_INDEX (0-based position on the item's timeline). Aggregate functions over a UID/list: SUM(operand), AVG(operand), COUNTA(operand), SUMIF(operand, condition_ident OP number), COUNTIF(...), AVGIF(...) — OP< <= > >= = !=, RHS must be a numeric literal. The magic identifier CURRENT_PERIOD returns the current period's end as a date serial — use it for time-gated patterns: store Hire Date as a date serial and write IF(Hire Date <= CURRENT_PERIOD, salary, 0) for hire ramps, contract activation, ramp-up, depreciation windows. The magic identifier LIST_INDEX (list-mode formulas only) is the current element's 0-based position in the list — auto-populate age/seniority offsets: cohort_lag = LIST_INDEX, then retention = retention_curve_M-$cohort_lag reads the curve at each cohort's age with no manual per-element inputs. Lag suffix on any UID or back-ticked name: _M-N, _Q-N, _Y-N (e.g. cash_eop_M-1, `Total Revenue`_Y-1); variable lag _M-$delay reads the offset from another item ($ + name or uid; rounded, clamped ≥ 0, converted at the reading item's grain). In a list-mode formula a list-dimensioned offset resolves PER ELEMENT (each element shifts by its own lag — the ramp-up/cohort primitive); a list offset that cannot project onto exactly one element is rejected (LIST_LAG_REF_AMBIGUOUS). Self-referencing lags are legal and the standard way to break instant cycles. Subscript syntax: Item[`key`] slices a list-dimensioned item by a list-item UID/label of its liste_ref (returns that element's scalar series), or by a key from any list reachable via mapped_to (auto-aggregates the matching source items — equivalent to a SUMIF over mapped_to). Lag suffixes also apply to a subscript: Salary[`Sales`]_Y-1, and Item[i]_M-1 is the current element's previous-period value — the per-element roll-forward (Headcount[i] = Hires[i] + Headcount[i]_M-1); a bare lagged self-ref in a list-mode formula is the list TOTAL re-added into every element (×N compounding, warning BARE_SELF_LAG_IN_LIST_FORMULA) — use [i]_M-N. MDK 0.7.0: a bare reference to a list-dimensioned item is its TOTAL (sum across the dimension) in EVERY context; wrap in SUM(...) only to also collapse across time. For the per-element value inside a list-mode formula (one whose own liste_ref is set), use the current-element subscript Item[i] — same-list → that element, cross-list → the items mapped to it. Example allocation: G&A[i] = Shared G&A * Revenue[i] / Revenue (per-element numerator, bare total denominator). Do NOT introduce a scalar callup to get a total — a bare reference already is the total. Not supported: string literals (use backtick-quoted identifiers or numeric literals), SUMIFS / COUNTIFS with multiple criteria (compose with a list-mode formula instead), user-defined functions, ternary ?: (use IF(...)). KPI items targeting monthly data may use annual shorthand like 2028, which normalizes to 2028-12. The meta op sets model identity: name, subtitle, finance_md, timelines, formats, default_branch, and glyph — the model emblem, a kebab-case Lucide icon name (https://lucide.dev/icons) validated at write time (unknown names rejected with suggestions; aliases like cash→banknote normalized; null → monogram). Formula-list (cross-dimension aggregation): a scalar formula whose operand carries a liste_ref auto-aggregates that operand by sum across its dimension; combined with mapped_to on the operand list, the subscript Operand[`key`] rolls up the source elements mapped to key (a SUMIF over the mapping). This is the most powerful Layerz pattern: prefer it over a parallel "code" column + SUMIF(..., = N). Inspect existing mappings via layerz_read (each list exposes mappings per element). Chart list rendering: list_mode (chart field) picks how list-backed series render — 'split' (default) explodes EVERY list-backed source into one series per list element (a single-source chart becomes the stacked breakdown, titled after the source; a multi-series chart prefixes each element with the item name — two 10-element lists = 20 legend rows), 'total' plots each item's aggregate as ONE series (chart a list total directly, no $item * 1 mirror formula needed). Pass compute with UIDs to get calculated values back. Not available for read-only API keys.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
opsYesMutation operations. Each op is a discriminated object on a string `op` field (e.g. {op:'create', role:'assumption', name:'Revenue'}; {op:'replace', from_uid:'abc', new_item:{role:'formula', name:'Revenue', formula:'xyz * 12'}}; {op:'update', uid:'abc', value:42}).
computeNoUIDs to compute after mutations
dry_runNoValidate and compute the batch without persisting it
summaryYesRequired: short human-readable note (3–100 chars) summarising what this change does. Shown as the label in the model version history. Be specific (e.g. "Renamed Revenue to Net Revenue", "Imported 2024 OPEX from Excel").
model_idNoTarget model UUID. Required for user-scoped API keys; ignored (or validated against scope) for model-scoped keys.
compute_branchNoReturn the `compute` values under a single branch's `[base, branch]` checkout instead of the stacked-all view (mirror of `layerz_read` `branch_id`). Pass `default` to audit the base plan alone — e.g. verify a base correction without a live import layer polluting the result. Requires `compute`. Does not change where the write lands (always the base).

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.4/5.0
Behavior5/5

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

The description discloses far more than the annotations convey: batch atomicity ("if one op fails validation ... nothing is persisted"), implicit auto-posing of bare-reference formulas as callup mirrors, rejection codes (FORMULA_EXPR_AND_CHILDREN, SCALAR_FUNCTION_MISUSE), idempotency of list_create by name, and warnings like BARE_SELF_LAG_IN_LIST_FORMULA. It also states "Not available for read-only API keys" and details list-mode value regimes that silently drop scalar values. Annotations (readOnlyHint=false, destructiveHint=true) align with "Mutate a model" — no contradiction.

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

Conciseness2/5

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

The description is an enormous, roughly 2,000-word wall of text with no headings, bullets, or section breaks. It duplicates schema content nearly verbatim (the role enum guidance appears in both the description and the schema's role fields) and buries important distinctions (op types, formula grammar, list mechanics, meta, chart rendering) in a single continuous stream. While technically dense, it is not appropriately sized or scannable for an agent that must quickly select and invoke the tool.

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

Completeness4/5

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

For a tool of this complexity — 12 op types, a full formula language, list-mode rules, callup semantics, and meta fields — the description covers essentially all input and behavioral context, including compute/dry_run/compute_branch usage and cross-tool references. The only notable gap is the success return payload: there is no output schema, and while "Pass compute with UIDs to get calculated values back" hints at output, the standard response (e.g., real UIDs for temp ids, health results) is never documented.

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?

Schema coverage is 100%, so the baseline is 3, but the description adds meaningful semantics absent from the schema: ops must be JSON objects with a string `op` field and never JSON-encoded strings; temp ids resolve inside formulas; scalar value/values are rejected on list-mode items and silently dropped; period-keyed maps merge into the existing series rather than replacing it; list_create is idempotent by name. These are exactly the details that prevent an agent from constructing invalid invocations.

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 first sentence states a specific verb and resource: "Mutate a model with batched ops" followed by the full op list (create/replace/update/delete/move/list_create/list_update/list_delete/meta). This unambiguously identifies the tool as the model-mutation companion to sibling tools like layerz_read and layerz_import_branch, with no ambiguity.

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

Usage Guidelines4/5

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

The description gives concrete routing guidance: dataset entities and imported actuals belong to layerz_import_branch ("imported actuals/overrides are import-owned (use layerz_import_branch or the Sources import)"), mappings and health should be inspected via layerz_read, and read-only API keys cannot use this tool. It also advises preferring replace over create+rewire+delete and nested sections over formula aggregators for structural grouping. The guidance is rich but scattered throughout rather than stated in a dedicated when-to-use section.

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.

Resources