MPS Actions Aspect

SkillDev tools

Use when defining or editing MPS node factories (the "actions" aspect) — `NodeFactories` roots, per-concept `NodeFactory` setup functions that initialize a freshly created node and optionally copy data from a replaced `sampleNode`, plus the actions aspect's `CopyPasteHandlers` and `PasteWrappers` roots. Reach for this skill when a substitution, side transform, completion replacement, or `add new initialized(...)` should preserve fields from the node it is replacing, or when defaults set in a constructor are not enough.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the MPS Actions Aspect skill

What this skill tells your AI

The instructions your AI receives, as published by jetbrains/mps in .agents/skills/mps-aspect-actions/SKILL.md and read by ahel’s review.

The actions aspect customizes how new nodes of a concept are constructed by the MPS editor — typically during substitution, side transformations, right-transform/<ctrl-space> replacement, or auto-replace. The framework produces an empty instance of the target concept; the setup function then populates it, often copying fields from the node it is replacing (sampleNode). Lives in <lang>/models/<lang>.actions.mps, language jetbrains.mps.lang.actions. The aspect also hosts copy/paste handlers and paste wrappers (see references/copy-paste-and-paste-wrappers.md).

Critical Directives

  • One NodeFactories root per actions model; add per-concept NodeFactory children to it. Do not create one root per concept.
  • One NodeFactory per applicableConcept. Multiple factories for the same concept are not merged.
  • The setup function runs before newNode is attached to the model. newNode.parent, ancestors, and model-wide queries return null/empty. Use the enclosingNode implicit parameter when you need the would-be parent.
  • sampleNode is typed as node<BaseConcept>. Always narrow with ifInstanceOf (sampleNode is <Concept> original) before accessing fields — direct dereference will NPE or fail to compile.
  • Use SPropertyAccess (ref propertyPropertyDeclaration) for properties; use SLinkAccess (ref linkLinkDeclaration) for both containment and reference child roles.
  • Factories do not fire for quotations <C()>, model.add root(<C()>), model.add new root(C), or pure rendering. If you need factory-initialised nodes in generator code, use new node<C>() with explicit property assignments or route creation through an intention that calls add new initialized.
  • Edit factories through MPS MCP tools (mps_mcp_insert_root_node_from_json, mps_mcp_update_node, optionally mps_mcp_parse_java_and_insert with featureKind: "STATEMENTS" for the body). Do not hand-edit .mps files.
  • After editing, validate with mps_mcp_check_root_node_problems on the NodeFactories root and rebuild the language.

Common-Path Workflow

  1. Ensure an actions model exists (<lang>/models/<lang>.actions.mps; create with mps_mcp_create_model and modelName: "<lang>.actions" — aspect ID actions, case-sensitive, no @ suffix; see aspect-model-stereotypes.md). Used languages: jetbrains.mps.lang.actions, jetbrains.mps.baseLanguage, jetbrains.mps.lang.smodel. Add jetbrains.mps.lang.core as the base. Import the structure model of your language.
  2. Create the NodeFactories root via mps_mcp_insert_root_node_from_json (blueprint in references/json-blueprints.md). Set name.
  3. For each concept that needs custom initialization, add a NodeFactory child via mps_mcp_update_node. Set applicableConcept; attach a NodeSetupFunction with a StatementList body.
  4. Fill the body. Typical pattern: ifInstanceOf (sampleNode is <Concept> original) { newNode.<prop> = original.<prop>; ... }. Cross-type narrowing is allowed — see references/setup-function-bodies.md.
  5. Validate with mps_mcp_check_root_node_problems, rebuild the language, exercise in a sandbox.

Implicit Parameters of NodeSetupFunction

AliasFQNTypeNullableNotes
newNodejetbrains.mps.lang.actions.structure.NodeSetupFunction_NewNodenode<applicableConcept>noThe freshly created empty node. Populate it.
sampleNodejetbrains.mps.lang.actions.structure.NodeSetupFunction_SampleNodenode<BaseConcept>yesNode being replaced, if any. Narrow with ifInstanceOf.
enclosingNodejetbrains.mps.lang.actions.structure.NodeSetupFunction_EnclosingNodenode<BaseConcept>yesProspective parent container; null during root-node creation or programmatic creation outside the editor.
indexjetbrains.mps.lang.actions.structure.NodeSetupFunction_Indexint0-based position in the parent collection, or 0 if not in a collection.
model(plain VariableReference in generated Java)SModelyesThe SModel newNode will be inserted into. Most factories don't use this.

Signature surfaced in the editor: (newNode, sampleNode, enclosingNode, index, model) -> void.

Related Skills

  • mps-aspect-behavior — concept constructors are the place to set defaults that don't depend on the node being replaced. Use a constructor for intrinsic defaults, a NodeFactory for context-sensitive carry-over.
  • mps-aspect-intentions — intentions that call add new initialized(...) route through factories. The NF_* family is documented in the intentions skill's references/factory-initialized.md.
  • mps-aspect-generator — generator output bypasses factories (quotations and add new root do not fire factories). Document where the generator must mimic the factory behavior.
  • mps-model-manipulation — full smodel reference: SLinkAccess vs SPropertyAccess, IfInstanceOfStatement, IfInstanceOfVariable, and the NF_* family.
  • mps-aspect-structure-concepts — when adding the concept that applicableConcept targets.
  • mps-aspect-constraints — for canBe* rules that gate insertion before a factory ever runs.

Reference Index

  • Open references/node-factories-and-triggers.md when you need the validated concept ref for NodeFactories / NodeFactory / NodeSetupFunction, the rule that factories are picked by exact applicableConcept (not by subtype inheritance through a more-general parent factory), and the precise list of triggers that fire / do not fire a factory.
  • Open references/setup-function-bodies.md when writing the function body — copying properties vs. child links (SPropertyAccess vs SLinkAccess), cross-type narrowing in ifInstanceOf, the verbatim ChemMastery (same-type property copy) and Kaja (cross-type child-link copy) examples, and the JSON shape for an AssignmentExpression over SLinkAccess.
  • Open references/json-blueprints.md when inserting the NodeFactories root or a NodeFactory child via MCP — minimal blueprints, the full IfInstanceOfStatement body, and the tip on using mps_mcp_parse_java_and_insert to skip the blueprint.
  • Open references/copy-paste-and-paste-wrappers.md when the language needs custom copy pre-processing, paste post-processing, or paste-time wrapping of nodes — these other roots also live in the actions aspect.
  • Open references/concept-reference.md for the validated FQN/ref table — the jetbrains.mps.lang.actions concepts plus the supporting BaseLanguage and smodel concepts used in setup bodies.
  • Open references/common-failures.md when a freshly created node always has defaults, sampleNode.<prop> does not compile, enclosingNode is unexpectedly null, the factory fires twice, or it does not fire from quotations / generator code.

Signals

GitHub stars
2k
Forks
309
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
mps-aspect-actions
Source
github.com/jetbrains/mps