Skip to main content

Prompts

Prompts are reusable templates for LLM interactions. They help you generate consistent, context-aware prompts for AI agents.

The @Prompt Decorator

The @Prompt decorator marks a method as an MCP prompt template with automatic name inference.

Basic Usage

import { Prompt } from '@leanmcp/core';

export class GreetingService {
  @Prompt({ description: 'Generate a greeting prompt' })
  greetingPrompt(args: { name?: string }) {
    return {
      messages: [{
        role: 'user',
        content: { 
          type: 'text', 
          text: `Say hello to ${args.name || 'there'}!` 
        }
      }]
    };
  }
}
Key Points:
  • Prompt name is automatically derived from method name (greetingPrompt)
  • Input schema can be explicitly defined via inputClass or inferred from parameter type
  • Returns message array following MCP prompt format

Prompt Definition

Type Signature

From packages/core/src/decorators.ts:122-141:
export interface PromptOptions {
  description?: string;
  inputClass?: any; // Optional: Explicit input class for schema generation
}

export function Prompt(options: PromptOptions = {}): MethodDecorator

Options

OptionTypeRequiredDescription
descriptionstringNoHuman-readable description of the prompt’s purpose
inputClassClassNoClass defining input schema (can be inferred from parameter type)

Message Format

Standard Message Structure

Prompts must return an object with a messages array following the MCP specification:
interface PromptMessage {
  role: 'user' | 'assistant' | 'system';
  content: {
    type: 'text' | 'image' | 'resource';
    text?: string;
    data?: string;
    mimeType?: string;
  };
}

interface PromptResult {
  messages: PromptMessage[];
  description?: string;
}

Text Messages

@Prompt({ description: 'Generate a code review prompt' })
codeReviewPrompt(args: { code: string; language: string }) {
  return {
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: 'You are an expert code reviewer.'
        }
      },
      {
        role: 'user',
        content: {
          type: 'text',
          text: `Review this ${args.language} code:\n\n${args.code}`
        }
      }
    ]
  };
}

Multi-Turn Conversations

@Prompt({ description: 'Generate a debugging conversation' })
debuggingPrompt(args: { error: string; context: string }) {
  return {
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: 'You are a debugging assistant.'
        }
      },
      {
        role: 'user',
        content: {
          type: 'text',
          text: `I'm getting this error: ${args.error}`
        }
      },
      {
        role: 'assistant',
        content: {
          type: 'text',
          text: 'I can help debug that. Can you provide more context?'
        }
      },
      {
        role: 'user',
        content: {
          type: 'text',
          text: args.context
        }
      }
    ]
  };
}

Prompt Templates

Input Schema Definition

Define input classes for type-safe prompt parameters:
class ComposeMessagePromptInput {
  @SchemaConstraint({ description: 'Purpose of the message' })
  purpose!: string;

  @Optional()
  @SchemaConstraint({ description: 'Tone (professional, casual, formal)' })
  tone?: string;

  @Optional()
  @SchemaConstraint({ description: 'Additional context' })
  context?: string;
}

@Prompt({ 
  description: 'Generate a professional message template',
  inputClass: ComposeMessagePromptInput
})
composeMessagePrompt(args: ComposeMessagePromptInput) {
  const tone = args.tone || 'professional';
  const context = args.context ? `\n\nContext: ${args.context}` : '';
  
  return {
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `Compose a ${tone} message for: ${args.purpose}${context}`
      }
    }]
  };
}

Real-World Examples

Slack Message Composer

From examples/slack-with-auth/mcp/slack/index.ts:454-473:
class ComposeMessagePromptInput {
  @SchemaConstraint({ description: 'Purpose of the message' })
  purpose!: string;

  @Optional()
  @SchemaConstraint({ description: 'Tone of the message' })
  tone?: string;

  @Optional()
  @SchemaConstraint({ description: 'Additional context' })
  context?: string;
}

@Prompt({ 
  description: 'Generate a professional Slack message template',
  inputClass: ComposeMessagePromptInput
})
composeMessagePrompt(args: ComposeMessagePromptInput) {
  const tone = args.tone || 'professional';
  const context = args.context || '';
  
  return {
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `Compose a ${tone} Slack message for: ${args.purpose}${
          context ? `\n\nContext: ${context}` : ''
        }\n\nThe message should be clear, concise, and appropriate for workplace communication.`
      }
    }]
  };
}

Channel Description Generator

From examples/slack-with-auth/mcp/slack/index.ts:479-495:
class ChannelDescriptionPromptInput {
  @SchemaConstraint({ description: 'Name of the channel' })
  channelName!: string;

  @SchemaConstraint({ description: 'Purpose of the channel' })
  channelPurpose!: string;
}

@Prompt({ 
  description: 'Generate a clear Slack channel description',
  inputClass: ChannelDescriptionPromptInput
})
channelDescriptionPrompt(args: ChannelDescriptionPromptInput) {
  return {
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `Create a concise channel description for "${args.channelName}" 
        with purpose: ${args.channelPurpose}\n\n
        The description should be 1-2 sentences.`
      }
    }]
  };
}

Code Generation Prompt

class CodeGenPromptInput {
  @SchemaConstraint({ description: 'Programming language' })
  language!: string;

  @SchemaConstraint({ description: 'Task description' })
  task!: string;

  @Optional()
  @SchemaConstraint({ description: 'Code style preferences' })
  style?: string;
}

@Prompt({ 
  description: 'Generate code based on task description',
  inputClass: CodeGenPromptInput
})
codeGenerationPrompt(args: CodeGenPromptInput) {
  const styleNote = args.style ? `\nStyle: ${args.style}` : '';
  
  return {
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: `You are an expert ${args.language} programmer.`
        }
      },
      {
        role: 'user',
        content: {
          type: 'text',
          text: `Task: ${args.task}${styleNote}\n\nProvide clean, well-documented code.`
        }
      }
    ]
  };
}

Advanced Patterns

Dynamic System Prompts

@Prompt({ description: 'Sentiment analysis with custom parameters' })
sentimentPrompt(args: { text: string; targetAudience?: string }) {
  const audience = args.targetAudience || 'general audience';
  
  return {
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: `Analyze sentiment considering ${audience} perspective.`
        }
      },
      {
        role: 'user',
        content: {
          type: 'text',
          text: args.text
        }
      }
    ]
  };
}

Contextual Prompts

@Prompt({ description: 'Generate contextual help prompt' })
helpPrompt(args: { feature: string; userLevel: 'beginner' | 'advanced' }) {
  const complexity = args.userLevel === 'beginner' 
    ? 'simple, step-by-step instructions'
    : 'detailed technical explanation';
  
  return {
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `Explain ${args.feature} using ${complexity}.`
      }
    }]
  };
}
The @Prompt decorator supports automatic type inference:
// Explicit inputClass (recommended)
@Prompt({ 
  description: 'Generate prompt',
  inputClass: PromptInput 
})
myPrompt(args: PromptInput) { /* ... */ }

// Inferred from parameter type
@Prompt({ description: 'Generate prompt' })
myPrompt(args: PromptInput) { /* ... */ }
From packages/core/src/decorators.ts:131-139, the decorator will:
  1. Use explicit inputClass if provided
  2. Fall back to parameter type inference using reflect-metadata

Best Practices

Prompt Engineering Tips:
  • Be specific and clear in your instructions
  • Provide examples of desired output format
  • Use system messages to set consistent behavior
  • Include relevant context from input parameters
  • Keep prompts focused on a single task

Template Reusability

export class PromptService {
  // Base prompt template
  private baseInstruction(tone: string) {
    return `You are a ${tone} communication assistant.`;
  }

  @Prompt({ description: 'Professional email prompt' })
  emailPrompt(args: { purpose: string }) {
    return {
      messages: [
        {
          role: 'system',
          content: { type: 'text', text: this.baseInstruction('professional') }
        },
        {
          role: 'user',
          content: { type: 'text', text: `Write an email about: ${args.purpose}` }
        }
      ]
    };
  }

  @Prompt({ description: 'Casual message prompt' })
  messagePrompt(args: { purpose: string }) {
    return {
      messages: [
        {
          role: 'system',
          content: { type: 'text', text: this.baseInstruction('friendly') }
        },
        {
          role: 'user',
          content: { type: 'text', text: `Write a message about: ${args.purpose}` }
        }
      ]
    };
  }
}
Prompt names are automatically derived from method names using String(propertyKey). Choose descriptive names that clearly indicate the prompt’s purpose.

Prompts vs Tools

FeaturePromptsTools
PurposeGenerate LLM interaction templatesExecute actions and return results
Return ValueMessage arrayArbitrary data
Side EffectsNone (template only)Can modify state, call APIs
Use CaseGuide AI conversationsPerform operations

Next Steps

Build docs developers (and LLMs) love