mcp-rune 0.107.0
Be star #1 Get started
SECTION III · GUIDE 12 OF 49
Reading
11 min
Topic
architecture
Spec
v0.107.0
Source
03-the-prompt/prompt-derivation.md
On this page10 sections

Customization: the 5-layer pipeline itself is not replaceable — these layers explain how the framework turns your model + prompt config into a string. The deployer-facing API documented in this chapter is PromptContentBuilder, which you call inside your BasePrompt.promptContent to assemble the result. You consume the pipeline; you do not replace it.

Prompt derivation

Chapter 2’s derivation overview listed what the framework derives from your Model. This chapter walks the path the prompt subsystem actually takes — five layers between the static attributes block you wrote and the string an LLM ends up reading. Knowing the layers matters when you want to override one (a custom kind label, a section-level intro) without forking the rest.

A 5-layer architecture for generating prompt documentation from model and prompt configuration, eliminating manual duplication and ensuring consistency.

Table of Contents

Architecture Overview

PROMPT DERIVATION · FIVE LAYERS5Layer 5: BEHAVIORAL— generateStatefulGuidanceInstructions()(BasePrompt) Turn-taking, validation, mode selection4Layer 4: ASSEMBLY— PromptContentBuilder.build()Composes all layers into final promptContent3Layer 3: SECTION DOCS— PromptContentBuilder + BasePromptPer-section field tables, enum tables, content notes2Layer 2: GROUPING— sections + fieldGroups(BasePrompt static config) Workflow structure, field org1Layer 1: SCHEMA— derivePromptSchema()(schema-derivation.js) fieldDefinitions from model configdata flows bottom-up ↑

Data flows bottom-up: Model config → field definitions → grouped sections → assembled documentation → behavioral instructions.

Layer 1: Schema Derivation

File: src/mcp/model-layer/schema-derivation.ts (public import: @mcp-rune/mcp-rune/prompts)

Generates fieldDefinitions from model’s attributes. This is the foundation — all field metadata comes from the model.

src/schema.ts
import { derivePromptSchema } from '@mcp-rune/mcp-rune/prompts'
import { Activity } from '../models/index.js'

static {
  const schema = derivePromptSchema(Activity, {
    fieldGroups: this.fieldGroups,
    fieldOverrides: {
      // Override/extend fields from model
      theme_id: { required: true }
    },
    promptFields: {
      // Prompt-only fields not in model
      book_ids: { name: 'book_ids', type: 'array', required: false }
    }
  })

  this.fieldGroups = schema.fieldGroups
  this.fieldDefinitions = schema.fieldDefinitions
}
import { derivePromptSchema } from '@mcp-rune/mcp-rune/prompts'
import { Activity } from '../models/index.js'

static {
  const schema = derivePromptSchema(Activity, {
    fieldGroups: this.fieldGroups,
    fieldOverrides: {
      // Override/extend fields from model
      theme_id: { required: true }
    },
    promptFields: {
      // Prompt-only fields not in model
      book_ids: { name: 'book_ids', type: 'array', required: false }
    }
  })

  this.fieldGroups = schema.fieldGroups
  this.fieldDefinitions = schema.fieldDefinitions
}

Key principle: The model’s attributes is the single source of truth. derivePromptSchema() reads type, description, examples, enumValues, enumDescriptions, default, validation, conditional, and required from the model and assembles them into fieldDefinitions.

Layer 2: Grouping

File: Prompt class static properties

Detailed reference: Sections & Field Groups guide. This section presents grouping as a layer of the derivation pipeline; the linked guide is the canonical reference for the two structures themselves.

Two complementary structures organize fields:

Sections (User-facing)

examples/prompt-derivation-framework-guide-02.ts
static sections = {
  classification: {
    title: 'Classification',
    description: 'Theme and category',
    required: true,
    groups: ['classification'],
    content: {
      intro: 'Classification determines how activities are organized.',
      notes: ['Use find_records to look up themes']
    }
  }
}
static sections = {
  classification: {
    title: 'Classification',
    description: 'Theme and category',
    required: true,
    groups: ['classification'],
    content: {
      intro: 'Classification determines how activities are organized.',
      notes: ['Use find_records to look up themes']
    }
  }
}

FieldGroups (Validation)

examples/prompt-derivation-framework-guide-03.ts
static fieldGroups = {
  classification: {
    fields: ['theme_id', 'category_id'],
    context: 'Classification',
    required: true
  }
}
static fieldGroups = {
  classification: {
    fields: ['theme_id', 'category_id'],
    context: 'Classification',
    required: true
  }
}

Section content enrichment: The content.intro and content.notes properties are automatically included in generated section documentation (Layer 3). Use these to add domain-specific context without writing custom section generator methods:

  • content.intro (string) — Rendered before the field table. Supports full markdown.
  • content.notes (string[]) — Rendered as a bullet list after the field table and enum tables.
  • askPrompt (string) — Custom “Ask the user: …” prompt at the end of the section.

Key principle: Prefer content.intro/content.notes over customSections overrides in allSections(). This keeps domain content in configuration while the framework auto-generates field tables and enum tables alongside it.

Layer 3: Section Documentation

Files: src/mcp/prompts/generators/, src/mcp/prompts/prompt-content-builder.ts

Generates per-section documentation from config. Includes:

  • Field tables (name, required, description)
  • Enum value tables (from enumDescriptions in model config)
  • Section intro text (from content.intro)
  • Section notes (from content.notes)
  • “Ask the user” prompts
  • Validation reminders

Generator functions

Rendering lives in pure functions under src/mcp/prompts/generators/, reached only through PromptContentBuilder — never import a generator directly:

FunctionFileOutput
generateSection / generateAllSectionsgenerators/section-generator.tsComplete section doc(s) with field table, enum tables, ask prompt
renderEnumTable / renderEnumTablesgenerators/helpers.tsMarkdown table(s) of enum values with descriptions
generateAttributeReferencegenerators/attribute-reference-generator.tsFull attribute reference table
generateSummarygenerators/summary-generator.tsStandard summary/confirmation section

Enum Tables

When a model field has enumDescriptions, enum tables are automatically generated:

examples/prompt-derivation-framework-guide-04.ts
// In model:
static attributes = {
  status: {
    type: 'enum',
    enumValues: ['planned', 'active', 'paused', 'completed', 'archived'],
    default: 'planned',
    enumDescriptions: {
      planned: 'Not yet started',
      active: 'Currently in progress',
      paused: 'Temporarily on hold',
      completed: 'Finished',
      archived: 'No longer relevant'
    }
  }
}
// In model:
static attributes = {
  status: {
    type: 'enum',
    enumValues: ['planned', 'active', 'paused', 'completed', 'archived'],
    default: 'planned',
    enumDescriptions: {
      planned: 'Not yet started',
      active: 'Currently in progress',
      paused: 'Temporarily on hold',
      completed: 'Finished',
      archived: 'No longer relevant'
    }
  }
}

Generated output:

**`status` values:**

| Value         | Description                   |
| ------------- | ----------------------------- |
| `"planned"`   | Not yet started **(default)** |
| `"active"`    | Currently in progress         |
| `"paused"`    | Temporarily on hold           |
| `"completed"` | Finished                      |
| `"archived"`  | No longer relevant            |

Layer 4: Assembly Pipeline

File: src/mcp/prompts/prompt-content-builder.ts

The PromptContentBuilder builder composes all layers into final promptContent.

examples/prompt-derivation-framework-guide-05.ts
get promptContent() {
  return PromptContentBuilder.for(ActivityPrompt, 'activity')
    .add(`# Activity Creation Guide

## What is an Activity?
Custom intro text...`)
    .standard()           // flowDiagram → guidance → allSections → summary
    .toolUsage()          // Auto-generated tool docs
    .attributeReference() // Layer 3: attribute reference table
    .build()              // Join with '\n\n---\n\n'
}
get promptContent() {
  return PromptContentBuilder.for(ActivityPrompt, 'activity')
    .add(`# Activity Creation Guide

## What is an Activity?
Custom intro text...`)
    .standard()           // flowDiagram → guidance → allSections → summary
    .toolUsage()          // Auto-generated tool docs
    .attributeReference() // Layer 3: attribute reference table
    .build()              // Join with '\n\n---\n\n'
}

Parts are joined with \n\n---\n\n (horizontal rules) by default.

Layer 5: Behavioral

File: src/mcp/prompts/generators/guidance-generator.tsgenerateGuidance()

Only applies to stateful prompts. Generates:

  • Mode selection (guided vs quick)
  • Turn-taking enforcement rules
  • Section-by-section validation requirements
  • Forbidden/correct behavior patterns

Accessed via .guidance() in the builder, which .standard() already includes.

PromptContentBuilder API

Factory

examples/prompt-derivation-framework-guide-06.ts
PromptContentBuilder.for(PromptClass, 'model_name')
PromptContentBuilder.for(PromptClass, 'model_name')

Builder Methods

MethodDescriptionUse With
.add(content)Add custom markdown contentAll strategies
.standard(options?)Canonical: flowDiagram → guidance → beforeSections → allSections → summaryAll
.flowDiagram()Step-by-step roadmap from sections/fieldGroups configAll strategies
.guidance()Stateful guidance instructions (Layer 5)Stateful only
.section(groupName, num, options)Single section documentationStateful
.allSections({ skip, customSections })All sections from configStateful
.summary()Standard summary/confirmation templateStateful
.toolUsage(overrides?)Auto-generated tool usage docs from static toolUsage configAll strategies
.attributeReference()Auto-generated attribute tableAll strategies
.build(separator)Join parts (default: \n\n---\n\n)All

.allSections() Options

examples/prompt-derivation-framework-guide-07.ts
.allSections({
  skip: ['content'],  // Skip sections handled by custom .add() calls
  customSections: {
    // Override specific sections with custom generators
    resources: (sectionNum) => `## SECTION ${sectionNum}: Resources\n...custom content...`
  }
})
.allSections({
  skip: ['content'],  // Skip sections handled by custom .add() calls
  customSections: {
    // Override specific sections with custom generators
    resources: (sectionNum) => `## SECTION ${sectionNum}: Resources\n...custom content...`
  }
})

Migration Guide

Before (manual documentation)

examples/prompt-derivation-framework-guide-08.ts
get promptContent() {
  return `
# My Guide
...intro...

| Field | Required | Description |
|-------|----------|-------------|
| name | Yes | The name |        ← Hardcoded, will drift from model
| type | No | The type |

## Summary
...manual summary...

## Attribute Reference
${this.generateAttributeReference()}  ← Custom method per prompt
`
}
get promptContent() {
  return `
# My Guide
...intro...

| Field | Required | Description |
|-------|----------|-------------|
| name | Yes | The name |        ← Hardcoded, will drift from model
| type | No | The type |

## Summary
...manual summary...

## Attribute Reference
${this.generateAttributeReference()}  ← Custom method per prompt
`
}

After (framework)

examples/prompt-derivation-framework-guide-09.ts
import { PromptContentBuilder } from '@mcp-rune/mcp-rune/prompts'

get promptContent() {
  return PromptContentBuilder.for(MyPrompt, 'my_model')
    .add(`# My Guide\n\n...intro...`)
    .standard()
    .toolUsage()
    .attributeReference()  // One line replaces 20+ lines
    .build()
}
import { PromptContentBuilder } from '@mcp-rune/mcp-rune/prompts'

get promptContent() {
  return PromptContentBuilder.for(MyPrompt, 'my_model')
    .add(`# My Guide\n\n...intro...`)
    .standard()
    .toolUsage()
    .attributeReference()  // One line replaces 20+ lines
    .build()
}

Migration Steps

  1. Add import { PromptContentBuilder } from '@mcp-rune/mcp-rune/prompts'
  2. Replace promptContent getter with builder pipeline
  3. Remove generateAttributeReference() → replaced by .attributeReference()
  4. Remove generateSummarySection() → replaced by .summary() (stateful)
  5. Keep domain-specific methods (tool usage, custom sections) as .add() calls
  6. Update tests if they check for specific format strings

Content Categories

When migrating, classify each piece of content:

CategoryDescriptionAction
A: Auto-generatableSummary templates, attribute referencesReplace with .summary(), .attributeReference()
B: Config-generatableSection documentation, enum tablesUse .allSections() or .section(), enrich content.notes
C: CustomIntro text, tool usage, domain-specific logicKeep as .add() calls

Rule of thumb: If the content depends only on fieldDefinitions, fieldGroups, or sections, it’s auto-generatable. If it requires runtime state or domain knowledge, keep it as .add().