mcp-rune 0.107.0
Be star #1 Get started
SECTION III · GUIDE 14 OF 49
Reading
9 min
Topic
advanced
Spec
v0.107.0
Source
03-the-prompt/stateful.md
On this page7 sections

Stateful Strategies

For complex models with many fields organized into sections, mcp-rune ships a stateful strategy that walks the agent through one section at a time, validating as it goes. Use this when a single-shot validation would overwhelm the LLM — typically 20+ fields, multiple required dependencies, or workflows where the user needs to see partial progress before committing.

This guide covers the configuration knobs, the validation flow, and the StatefulFormStrategy API. For the structure stateful strategies consume (sections + fieldGroups), see the Sections & Field Groups guide. For the comparison with stateless and hybrid, see the Prompt Creation guide.

The walk is per-section, not per-field:

STATEFUL PROMPT · SECTION PROGRESSIONget_prompt_guideSection 1basicstitledescriptionvalidateSection('basics')ok → advanceerrors → loop backadvanceSection 2classificationtheme_idcategory_idvalidateSection('classification')ok → advanceerrors → loop backadvanceSection 3timingoccurred_atduration_minutesvalidateSection('timing')ok → advancegenerateSummaryapply mutationcreate_model / update_modelgetProgress()◀ call status anytimecompleted:['basics', 'classification']pending: ['timing']

validateSection is the only validation the LLM calls during the walk — section-scoped, so an error in timing doesn’t reopen basics. getProgress is a status query the LLM can call at any point to know what’s done and what’s left.

Mode configuration

Stateful prompts support two interaction modes:

ModeBehaviorUse Case
guidedWalk through each section interactivelyHuman interaction via MCP client
quickMinimize questions, infer values from contextAgentic workflows, automation

Enabling mode selection

1. Add mode to static arguments

examples/stateful-strategies-guide-01.ts
static arguments = [
  { name: 'mode', description: '"guided" (step-by-step) or "quick" (minimal interaction)', required: false },
  // ... other prompt-specific arguments
]
static arguments = [
  { name: 'mode', description: '"guided" (step-by-step) or "quick" (minimal interaction)', required: false },
  // ... other prompt-specific arguments
]

2. Mode selection is automatic via the builder

The .guidance() step (included in .standard()) automatically includes mode-selection instructions.

Structural overview

The Flow diagram (generated by .flowDiagram()) serves as the single structural overview. In guided mode, the LLM references this diagram rather than repeating section information.

Stateful prompt structure

src/prompts/activity-prompt.ts
import { BasePrompt, derivePromptSchema, PromptContentBuilder } from '@mcp-rune/mcp-rune/prompts'

import { Activity } from '../models/index.js'

export class ActivityPrompt extends BasePrompt {
  // 1. Declare strategy
  static formStrategy = 'stateful'

  // 2. Define sections (user-facing workflow)
  static sections = {
    basics: {
      title: 'Basic Information',
      description: 'Title and description',
      required: true,
      groups: ['basics']
    }
  }

  // 3. Define field groups (validation structure)
  static fieldGroups = {
    basics: {
      fields: ['title', 'description'],
      required: true
    }
  }

  // 4. Schema derivation
  static {
    const schema = derivePromptSchema(Activity, {
      fieldGroups: this.fieldGroups
    })
    this.fieldGroups = schema.fieldGroups
    this.fieldDefinitions = schema.fieldDefinitions
  }

  // 5. MCP metadata
  static title = 'Create Activity'
  static modelName = 'activity'

  // 6. Arguments with mode support
  static arguments = [
    { name: 'mode', description: '"guided" or "quick"', required: false },
    { name: 'title', description: 'Activity title', required: false }
  ]

  // 7. Generate description using the strategy intro fragment
  static description = `${ActivityPrompt.getStrategyIntro()} activities.`

  // 8. Prompt content using PromptContentBuilder
  get promptContent() {
    return PromptContentBuilder.for(ActivityPrompt, 'activity')
      .add(`# Activity Creation Guide\n\n...`)
      .standard()
      .toolUsage()
      .attributeReference()
      .build()
  }
}
import { BasePrompt, derivePromptSchema, PromptContentBuilder } from '@mcp-rune/mcp-rune/prompts'

import { Activity } from '../models/index.js'

export class ActivityPrompt extends BasePrompt {
  // 1. Declare strategy
  static formStrategy = 'stateful'

  // 2. Define sections (user-facing workflow)
  static sections = {
    basics: {
      title: 'Basic Information',
      description: 'Title and description',
      required: true,
      groups: ['basics']
    }
  }

  // 3. Define field groups (validation structure)
  static fieldGroups = {
    basics: {
      fields: ['title', 'description'],
      required: true
    }
  }

  // 4. Schema derivation
  static {
    const schema = derivePromptSchema(Activity, {
      fieldGroups: this.fieldGroups
    })
    this.fieldGroups = schema.fieldGroups
    this.fieldDefinitions = schema.fieldDefinitions
  }

  // 5. MCP metadata
  static title = 'Create Activity'
  static modelName = 'activity'

  // 6. Arguments with mode support
  static arguments = [
    { name: 'mode', description: '"guided" or "quick"', required: false },
    { name: 'title', description: 'Activity title', required: false }
  ]

  // 7. Generate description using the strategy intro fragment
  static description = `${ActivityPrompt.getStrategyIntro()} activities.`

  // 8. Prompt content using PromptContentBuilder
  get promptContent() {
    return PromptContentBuilder.for(ActivityPrompt, 'activity')
      .add(`# Activity Creation Guide\n\n...`)
      .standard()
      .toolUsage()
      .attributeReference()
      .build()
  }
}

BasePrompt helpers

The rendering helpers that used to live on BasePrompt are now generator functions reached through PromptContentBuilder steps. What survives on BasePrompt is a small set of configuration-read statics:

StaticPurpose
getStrategyIntro()Strategy-appropriate intro fragment for static description
getSectionForGroup(groupName)Reverse lookup: section for a fieldGroup
getDefaults()Default values across all field definitions
toFormSchema()Transport-safe schema serialization for prompts/getFormSchema

Everything rendered — flow diagram, guidance, section docs, tool usage, attribute reference — comes from builder steps:

Builder stepRenders
.flowDiagram()Compact flow diagram from sections/fieldGroups
.guidance()Full guidance instructions including mode selection
.section(groupName, num) / .allSections()Per-section documentation
.toolUsage(overrides?)Tool usage examples from static toolUsage config
.attributeReference()Full attribute reference table

Validation flow

Stateful prompts require validation after each section:

User provides section input
    |
LLM calls validate_form(model, section, fields)
    |
Server validates and returns errors/warnings
    |
LLM proceeds to next section or asks for corrections
    |
After all sections: validate_form(model, fields) for full validation
    |
If ready_to_submit: true -> create_model()

StatefulFormStrategy API

The StatefulFormStrategy class handles stateful prompts with sections support.

getSections(promptClass)

Returns section metadata with aggregated fields from groups:

src/sections.ts
import { StatefulFormStrategy } from '@mcp-rune/mcp-rune/prompts'

const sections = StatefulFormStrategy.getSections(ActivityPrompt)
// Returns:
[
  {
    name: 'basics',
    title: 'Basic Information',
    required: true,
    fields: ['title', 'description'],
    groups: ['basics'],
    description: 'Title and description for the activity'
  },
  // ...
]
import { StatefulFormStrategy } from '@mcp-rune/mcp-rune/prompts'

const sections = StatefulFormStrategy.getSections(ActivityPrompt)
// Returns:
[
  {
    name: 'basics',
    title: 'Basic Information',
    required: true,
    fields: ['title', 'description'],
    groups: ['basics'],
    description: 'Title and description for the activity'
  },
  // ...
]

getProgress(promptClass, fields)

Returns completion progress by fieldGroup:

src/progress.ts
import { StatefulFormStrategy } from '@mcp-rune/mcp-rune/prompts'

const progress = StatefulFormStrategy.getProgress(ActivityPrompt, fields)
// Returns:
{
  sections: {
    basics: {
      applicable: true,
      total_fields: 2,
      filled_fields: 1,
      complete: false,
      partial: true,
      required: true,
      title: 'Basic Information'
    }
  },
  overall: {
    total_sections: 4,
    completed_sections: 1,
    required_complete: false,
    percentage: 25
  }
}
import { StatefulFormStrategy } from '@mcp-rune/mcp-rune/prompts'

const progress = StatefulFormStrategy.getProgress(ActivityPrompt, fields)
// Returns:
{
  sections: {
    basics: {
      applicable: true,
      total_fields: 2,
      filled_fields: 1,
      complete: false,
      partial: true,
      required: true,
      title: 'Basic Information'
    }
  },
  overall: {
    total_sections: 4,
    completed_sections: 1,
    required_complete: false,
    percentage: 25
  }
}

See also