Módulo 3: Tres Primitivas — Resources, Tools, Prompts

Prompts: Templates Reutilizables con Parámetros

Prompts: Templates Reutilizables con Parámetros

Descripción de la cápsula

La tercera primitiva de MCP es la menos intuitiva para developers: Prompts. No son funciones que ejecutan código ni datos que se leen. Son templates parametrizados que estandarizan cómo el usuario interactúa con el modelo a través del MCP server.

Piénsalo así: si un Resource es lo que el modelo puede ver y un Tool es lo que el modelo puede hacer, un Prompt es cómo el usuario le pide las cosas. Es la diferencia entre escribir "revisa mi código" (vago) y seleccionar un template de "Code Review" que ya incluye los criterios de revisión, el formato de output, y los aspectos a evaluar.

Los Prompts son como recetas predefinidas: el usuario elige una, llena los parámetros, y obtiene un prompt completo y optimizado listo para el modelo. Reducen la fricción y aseguran consistencia.


¿Qué es un Prompt en MCP?

Definición formal

Un Prompt en MCP es un template reutilizable con parámetros opcionales que el server expone al cliente. Cuando el usuario selecciona un prompt:

  1. El server lista los prompts disponibles (prompts/list)
  2. El usuario selecciona un prompt y llena los parámetros
  3. El server genera los mensajes del prompt con los parámetros aplicados (prompts/get)
  4. Los mensajes se insertan en la conversación con el modelo

Definición práctica

Un prompt responde a: "¿Qué interacciones predefinidas ofrece este server?"

Ejemplos de prompts:
├── code-review          → Review de código con criterios estándar
├── refactoring-plan     → Plan de refactoring paso a paso
├── sql-query-generator  → Generador de queries SQL a partir de lenguaje natural
├── bug-report           → Template estandarizado de bug report
├── api-documentation    → Generador de documentación de API
└── test-generator       → Generador de tests para una función

Características clave

CaracterísticaDetalle
TemplateTexto con placeholders que se rellenan con parámetros
ParámetrosArgumentos que el usuario proporciona (requeridos u opcionales)
Sin side effectsGenerar un prompt no modifica estado
Controlado por el usuarioEl usuario elige y parametriza — no el modelo
Multi-mensajePuede generar múltiples mensajes (system, user, assistant)
Contexto embebidoPuede incluir resources como contexto dentro del prompt

¿Por qué necesitas Prompts?

El problema que resuelven

Sin prompts, cada usuario escribe sus instrucciones de forma diferente:

Usuario A: "revisa este código"
Usuario B: "haz code review del archivo main.py"
Usuario C: "analiza el código buscando bugs, performance issues, y mejoras de legibilidad"

El usuario C obtiene mejores resultados porque su prompt es más específico. Pero ¿por qué cada usuario debería reinventar el prompt perfecto?

La solución: prompts estandarizados

Prompt: "code-review"
Parámetros: { file: "main.py", focus: "security" }

→ Genera automáticamente:
  "Realiza un code review del archivo main.py con enfoque en seguridad.
   Evalúa: inyección SQL, XSS, manejo de secrets, validación de inputs.
   Formato: lista de issues con severidad (critical/high/medium/low)."

Todos los usuarios obtienen la misma calidad de instrucción. El conocimiento de cómo pedir un buen code review queda encapsulado en el prompt.


Anatomía de un Prompt

Estructura de definición

interface PromptDefinition {
  name: string;                // "code-review"
  description?: string;        // "Realiza un code review profesional"
  arguments?: Array<{
    name: string;              // "filePath"
    description?: string;      // "Ruta del archivo a revisar"
    required?: boolean;        // true
  }>;
}

Estructura de respuesta

Cuando el cliente pide un prompt, el server retorna una lista de mensajes:

interface GetPromptResult {
  description?: string;
  messages: Array<{
    role: "user" | "assistant";
    content: {
      type: "text" | "resource";
      text?: string;
      resource?: {             // Resource embebido
        uri: string;
        mimeType?: string;
        text?: string;
      };
    };
  }>;
}

Un prompt puede retornar múltiples mensajes

Esta es una feature poderosa. Un prompt no solo genera un mensaje del usuario — puede generar una secuencia de mensajes que "prepara" la conversación:

messages: [
  {
    role: "user",
    content: {
      type: "text",
      text: "Eres un experto en seguridad de aplicaciones web."
    }
  },
  {
    role: "user",
    content: {
      type: "resource",
      resource: {
        uri: `file:///${filePath}`,
        text: fileContent,
        mimeType: "text/plain"
      }
    }
  },
  {
    role: "user",
    content: {
      type: "text",
      text: "Analiza este código buscando vulnerabilidades de seguridad..."
    }
  }
]

Implementación en TypeScript

Prompt básico

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "dev-tools",
  version: "1.0.0",
});

server.prompt(
  "code-review",
  "Realiza un code review profesional de un archivo",
  {
    filePath: z.string().describe("Ruta del archivo a revisar"),
    focus: z.enum(["general", "security", "performance", "readability"])
      .default("general")
      .describe("Área de enfoque del review"),
  },
  async ({ filePath, focus }) => {
    const criteria = {
      general: "bugs, mejoras de código, patrones, naming, y estructura",
      security: "inyección SQL, XSS, manejo de secrets, validación de inputs, y autenticación",
      performance: "complejidad algorítmica, uso de memoria, queries N+1, y caching",
      readability: "naming, comentarios, estructura, principio de responsabilidad única, y claridad",
    };

    return {
      messages: [
        {
          role: "user" as const,
          content: {
            type: "text" as const,
            text: `Realiza un code review profesional del archivo ${filePath}.

**Enfoque principal:** ${focus}

**Criterios de evaluación:**
${criteria[focus]}

**Formato de respuesta:**
Para cada issue encontrado, reporta:
1. **Línea(s):** número de línea o rango
2. **Severidad:** 🔴 Critical | 🟠 High | 🟡 Medium | 🔵 Low
3. **Descripción:** qué está mal y por qué
4. **Sugerencia:** cómo corregirlo con código ejemplo

Al final, incluye:
- **Resumen:** X issues encontrados (N critical, N high, etc.)
- **Puntuación general:** 1-10
- **Top 3 mejoras prioritarias**`,
          },
        },
      ],
    };
  }
);

Prompt con resource embebido

server.prompt(
  "refactoring-plan",
  "Genera un plan de refactoring para un archivo",
  {
    filePath: z.string().describe("Ruta del archivo a refactorizar"),
    goal: z.string().describe("Qué quieres mejorar (e.g., 'separar responsabilidades')"),
  },
  async ({ filePath, goal }) => {
    const fs = await import("fs/promises");
    let content: string;

    try {
      content = await fs.readFile(filePath, "utf-8");
    } catch {
      content = `[Error: no se pudo leer ${filePath}]`;
    }

    return {
      messages: [
        {
          role: "user" as const,
          content: {
            type: "resource" as const,
            resource: {
              uri: `file:///${filePath}`,
              text: content,
              mimeType: "text/plain",
            },
          },
        },
        {
          role: "user" as const,
          content: {
            type: "text" as const,
            text: `Genera un plan de refactoring para este archivo.

**Objetivo:** ${goal}

**El plan debe incluir:**
1. **Análisis actual:** qué hace el código y qué problemas tiene
2. **Estrategia de refactoring:** enfoque general
3. **Pasos específicos:** lista ordenada de cambios
4. **Código sugerido:** snippets de cómo quedaría cada parte
5. **Riesgos:** qué podría romperse y cómo mitigarlo
6. **Tests:** qué tests agregar antes de refactorizar`,
          },
        },
      ],
    };
  }
);

Prompt para generación de código

server.prompt(
  "generate-api-endpoint",
  "Genera código para un endpoint de API REST",
  {
    resource: z.string().describe("Nombre del recurso (e.g., 'users', 'products')"),
    operations: z.string().describe("Operaciones a generar: 'CRUD' o lista como 'create,read'"),
    framework: z.enum(["express", "fastapi", "hono"]).default("express").describe("Framework a usar"),
  },
  async ({ resource, operations, framework }) => {
    const ops = operations.toLowerCase() === "crud"
      ? ["create", "read", "update", "delete"]
      : operations.split(",").map(o => o.trim());

    return {
      messages: [
        {
          role: "user" as const,
          content: {
            type: "text" as const,
            text: `Genera código para un API endpoint del recurso "${resource}" con framework ${framework}.

**Operaciones requeridas:** ${ops.join(", ")}

**Requisitos:**
- Validación de inputs completa
- Error handling con códigos HTTP apropiados
- Tipado completo (TypeScript si es Express/Hono, type hints si es FastAPI)
- Comentarios explicativos en español
- Estructura lista para producción

**Para cada operación genera:**
1. Route handler / endpoint
2. Validación de request body/params
3. Respuesta con status code apropiado
4. Manejo de errores (404, 400, 500)`,
          },
        },
      ],
    };
  }
);

Implementación en Python

Prompt básico

from mcp.server.fastmcp import FastMCP
from mcp.types import TextContent

server = FastMCP("dev-tools")

@server.prompt()
async def code_review(file_path: str, focus: str = "general") -> str:
    """Realiza un code review profesional de un archivo.

    Args:
        file_path: Ruta del archivo a revisar
        focus: Área de enfoque (general, security, performance, readability)
    """
    criteria = {
        "general": "bugs, mejoras de código, patrones, naming, y estructura",
        "security": "inyección SQL, XSS, manejo de secrets, validación de inputs",
        "performance": "complejidad algorítmica, uso de memoria, queries N+1, caching",
        "readability": "naming, comentarios, estructura, responsabilidad única, claridad",
    }

    return f"""Realiza un code review profesional del archivo {file_path}.

**Enfoque principal:** {focus}

**Criterios de evaluación:**
{criteria.get(focus, criteria["general"])}

**Formato de respuesta:**
Para cada issue:
1. Línea(s) afectada(s)
2. Severidad: Critical | High | Medium | Low
3. Descripción del problema
4. Sugerencia con código ejemplo

Resumen final: total de issues, puntuación 1-10, top 3 prioridades."""

Prompt con contexto dinámico

@server.prompt()
async def explain_error(error_message: str, language: str = "python") -> str:
    """Explica un error de programación y sugiere soluciones.

    Args:
        error_message: El mensaje de error completo
        language: Lenguaje de programación (python, typescript, rust)
    """
    return f"""Analiza este error de {language} y ayúdame a resolverlo:

{error_message}


**Responde con:**
1. **Qué significa:** Explicación en español, sin jerga innecesaria
2. **Por qué ocurre:** Las causas más comunes de este error
3. **Cómo resolverlo:** Solución paso a paso con código
4. **Cómo prevenirlo:** Qué hacer para que no vuelva a pasar
5. **Ejemplo:** Código mínimo que reproduce y resuelve el error"""

Prompt para tests

@server.prompt()
async def generate_tests(file_path: str, framework: str = "pytest") -> str:
    """Genera tests para las funciones de un archivo.

    Args:
        file_path: Ruta del archivo a testear
        framework: Framework de testing (pytest, unittest, jest, vitest)
    """
    return f"""Genera tests completos para las funciones en {file_path} usando {framework}.

**Para cada función genera:**
1. Test del happy path (caso normal)
2. Tests de edge cases (inputs vacíos, nulos, extremos)
3. Tests de error cases (inputs inválidos, excepciones esperadas)
4. Test de tipo de retorno

**Estructura:**
- Nombres descriptivos: test_[function]_[scenario]_[expected_result]
- Arrange-Act-Assert pattern
- Fixtures/mocks cuando sea necesario
- Comentarios explicando el por qué de cada test"""

Prompts vs Instructions directas

¿Por qué no simplemente escribir el prompt?

Podrías argumentar: "Puedo escribir estas instrucciones yo mismo. ¿Para qué encapsularlas en un Prompt?"

AspectoEscribir cada vezPrompt MCP
ConsistenciaVaría según el día y la prisaSiempre la misma calidad
EficienciaReescribir es trabajo repetidoSeleccionar + parámetros
ConocimientoDepende de qué recuerdesEl expertise está en el template
CompartirDifícil de compartir con el equipoDisponible para todos en el server
EvoluciónCada quien mejora por su cuentaMejoras centralizadas benefician a todos
ContextoOlvidas incluir archivos/datosPuede embeber resources automáticamente

Cuándo usar Prompts vs Tools

¿El usuario necesita que el modelo EJECUTE algo?
  → Sí → Tool
  → No

¿El usuario necesita datos como CONTEXTO?
  → Sí → Resource
  → No

¿El usuario quiere una forma ESTANDARIZADA de pedir algo?
  → Sí → Prompt

Los prompts no reemplazan tools ni resources — los complementan. Un prompt puede incluir resources como contexto y generar instrucciones que lleven al modelo a usar tools.


Patrones avanzados

Patrón 1: Prompt con múltiples roles

server.prompt(
  "pair-programming",
  "Inicia una sesión de pair programming asistido",
  {
    taskDescription: z.string().describe("Descripción de la tarea a implementar"),
    expertise: z.enum(["junior", "mid", "senior"]).default("mid").describe("Nivel del developer"),
  },
  async ({ taskDescription, expertise }) => {
    const level = {
      junior: "Explica cada decisión en detalle, muestra alternativas, incluye best practices básicas",
      mid: "Enfócate en decisiones de diseño, patrones, y trade-offs. Asume conocimiento de sintaxis",
      senior: "Sé conciso, enfócate en arquitectura, edge cases, y performance. Sugiere sin explicar lo obvio",
    };

    return {
      messages: [
        {
          role: "user" as const,
          content: {
            type: "text" as const,
            text: `Vamos a hacer pair programming. Yo soy el driver, tú eres el navigator.

**Tarea:** ${taskDescription}

**Mi nivel:** ${expertise}
**Tu estilo:** ${level[expertise]}

**Reglas de la sesión:**
1. No escribas el código completo de una vez — guíame paso a paso
2. Después de cada paso, espera mi implementación antes de continuar
3. Si cometo un error, señálalo y explica por qué
4. Sugiere tests para cada pieza de funcionalidad
5. Al final, haz un resumen de lo que construimos

¿Empezamos? Dame el primer paso.`,
          },
        },
      ],
    };
  }
);

Troubleshooting

"El prompt no aparece en la lista de prompts"

Causa: El prompt no se registró correctamente o el host no soporta prompts.

Solución:

# Verifica con MCP Inspector
npx @modelcontextprotocol/inspector

# Busca la sección "Prompts" — debería listar tus prompts registrados
# Si no aparecen, verifica que server.prompt() se llama antes de connect()

"Los parámetros no se pasan correctamente"

Causa: El nombre del parámetro en la definición no coincide con el que usa el cliente.

Solución:

// Verifica que los nombres de parámetros son consistentes
server.prompt(
  "my-prompt",
  "Descripción",
  {
    filePath: z.string(),  // ← este nombre
  },
  async ({ filePath }) => {  // ← debe coincidir aquí
    // ...
  }
);

"El prompt genera mensajes vacíos"

Causa: La función retorna un array de mensajes vacío o con content undefined.

Solución:

// Asegúrate de que siempre retornas al menos un mensaje con contenido
return {
  messages: [
    {
      role: "user" as const,
      content: {
        type: "text" as const,
        text: "Este texto nunca debe estar vacío",
      },
    },
  ],
};

"El resource embebido no se incluye"

Causa: El path del archivo es incorrecto o el archivo no tiene permisos de lectura.

Solución:

// Agrega manejo de errores al leer archivos para el prompt
try {
  const content = await fs.readFile(filePath, "utf-8");
  // ... usar content
} catch (error) {
  // Fallback: informar al modelo que no se pudo leer
  messages.push({
    role: "user",
    content: {
      type: "text",
      text: `[No se pudo leer ${filePath}: ${error}. Procede sin el archivo.]`,
    },
  });
}

Ejercicios

Ejercicio 1: Identificar candidatos a Prompts (Fácil)

De esta lista de interacciones comunes, identifica cuáles serían buenos candidatos para un Prompt MCP:

  1. "Explica este error de Python"
  2. "Crea un archivo de configuración"
  3. "Haz un code review enfocado en performance"
  4. "¿Qué hora es?"
  5. "Genera documentación para esta función"
  6. "Elimina los archivos temporales"
Ver solución
  1. ✅ Prompt — Es un patrón repetible: "dado un error, explica y sugiere solución" con formato consistente
  2. ❌ Tool — Crea un archivo (side effect), no es un template de interacción
  3. ✅ Prompt — Code review con criterios estandarizados es un caso ideal para prompt
  4. ❌ Ni prompt ni resource/tool — No requiere MCP
  5. ✅ Prompt — Generar documentación con formato consistente es un patrón repetible
  6. ❌ Tool — Elimina archivos (side effect)

Regla: Un buen candidato a Prompt es una interacción que se repite frecuentemente, se beneficia de estructura/formato consistente, y donde el valor está en "cómo pides", no en "qué ejecutas."

Ejercicio 2: Diseñar parámetros de un Prompt (Medio)

Diseña los parámetros (nombre, tipo, descripción, requerido/opcional) para estos prompts:

  1. Un prompt de "Commit Message Generator"
  2. Un prompt de "SQL Query Generator"
Ver solución
// 1. Commit Message Generator
server.prompt(
  "commit-message",
  "Genera un mensaje de commit siguiendo conventional commits",
  {
    diff: z.string().describe("Output de 'git diff --staged' con los cambios"),
    type: z.enum(["feat", "fix", "refactor", "docs", "test", "chore"])
      .optional()
      .describe("Tipo de commit. Si no se especifica, se infiere del diff"),
    scope: z.string().optional().describe("Scope del cambio (e.g., 'auth', 'api', 'ui')"),
    language: z.enum(["en", "es"]).default("en").describe("Idioma del mensaje"),
  },
  async ({ diff, type, scope, language }) => { /* ... */ }
);

// 2. SQL Query Generator
server.prompt(
  "sql-query",
  "Genera una query SQL a partir de una descripción en lenguaje natural",
  {
    description: z.string().describe("Qué datos necesitas, en lenguaje natural"),
    dialect: z.enum(["postgresql", "mysql", "sqlite"]).default("postgresql").describe("Dialecto SQL"),
    tables: z.string().describe("Nombres de tablas disponibles, separados por coma"),
    includeExplanation: z.boolean().default(true).describe("Incluir explicación de la query"),
  },
  async ({ description, dialect, tables, includeExplanation }) => { /* ... */ }
);

Ejercicio 3: Implementar un Prompt en TypeScript (Medio)

Implementa el prompt "commit-message" del ejercicio anterior con la lógica completa:

Ver solución
server.prompt(
  "commit-message",
  "Genera un mensaje de commit siguiendo conventional commits",
  {
    diff: z.string().describe("Output de 'git diff --staged'"),
    type: z.enum(["feat", "fix", "refactor", "docs", "test", "chore"])
      .optional()
      .describe("Tipo de commit (se infiere si no se especifica)"),
    scope: z.string().optional().describe("Scope del cambio"),
    language: z.enum(["en", "es"]).default("en").describe("Idioma del mensaje"),
  },
  async ({ diff, type, scope, language }) => {
    const langInstruction = language === "es"
      ? "Escribe el mensaje en español"
      : "Write the message in English";

    const scopeStr = scope ? `(${scope})` : "";
    const typeStr = type ? `Tipo: ${type}` : "Infiere el tipo basándote en los cambios";

    return {
      messages: [
        {
          role: "user" as const,
          content: {
            type: "text" as const,
            text: `Genera un mensaje de commit para estos cambios siguiendo Conventional Commits.

**Diff:**
\`\`\`diff
${diff}
\`\`\`

**Instrucciones:**
- ${typeStr}
- Scope: ${scopeStr || "infiere del contexto"}
- ${langInstruction}
- Formato: \`type(scope): descripción breve\`
- La descripción debe ser imperativa ("add", no "added")
- Máximo 72 caracteres en la primera línea
- Si es necesario, agrega un body con más contexto
- Si hay breaking changes, inclúyelos

**Retorna SOLO el mensaje de commit, sin explicaciones adicionales.**`,
          },
        },
      ],
    };
  }
);

Ejercicio 4: Prompt con Resource embebido en Python (Medio)

Implementa un prompt en Python que lea un archivo y genere documentación para él:

Ver solución
from mcp.server.fastmcp import FastMCP
from mcp.types import UserMessage, TextContent

server = FastMCP("doc-generator")

@server.prompt()
async def generate_docs(file_path: str, doc_style: str = "jsdoc") -> list:
    """Genera documentación para las funciones de un archivo.

    Args:
        file_path: Ruta del archivo a documentar
        doc_style: Estilo de documentación (jsdoc, numpy, google, sphinx)
    """
    try:
        with open(file_path, "r") as f:
            content = f.read()
    except FileNotFoundError:
        content = f"[No se pudo leer el archivo: {file_path}]"

    styles = {
        "jsdoc": "JSDoc (/** @param {type} name - description */)",
        "numpy": "NumPy style (Parameters\\n----------)",
        "google": "Google style (Args:\\n    param: description)",
        "sphinx": "Sphinx style (:param name: description)",
    }

    return [
        UserMessage(content=TextContent(
            type="text",
            text=f"Archivo a documentar ({file_path}):\n\n```\n{content}\n```"
        )),
        UserMessage(content=TextContent(
            type="text",
            text=f"""Genera documentación completa para este archivo.

**Estilo:** {styles.get(doc_style, doc_style)}

**Para cada función/clase documenta:**
1. Descripción de qué hace (una línea)
2. Descripción detallada (si aplica)
3. Parámetros con tipo y descripción
4. Valor de retorno con tipo y descripción
5. Excepciones que puede lanzar
6. Ejemplo de uso

**Reglas:**
- Documentación en español
- Incluye tipos precisos, no 'any' ni 'object'
- Los ejemplos deben ser ejecutables"""
        )),
    ]

Resumen

En esta cápsula aprendiste:

  • Prompts son templates reutilizables con parámetros que estandarizan interacciones
  • Encapsulan expertise — el conocimiento de "cómo pedir bien" queda en el template
  • Controlados por el usuario — a diferencia de tools, el usuario elige cuándo usar un prompt
  • Pueden generar múltiples mensajes incluyendo resources embebidos como contexto
  • En TypeScript se registran con server.prompt() y Zod para parámetros
  • En Python se registran con el decorador @server.prompt() y type hints
  • Son ideales para: code reviews, generación de docs, templates de commit, planes de refactoring
  • No reemplazan Tools ni Resources — los complementan estandarizando cómo se piden las cosas

Próxima cápsula: Combinando Primitivas — cómo Resources, Tools y Prompts trabajan juntos en un MCP server real.


Recursos adicionales

  1. MCP Specification — Prompts - Especificación oficial de Prompts
  2. MCP TypeScript SDK — Prompts - Implementación en TypeScript
  3. MCP Python SDK — Prompts - Implementación en Python
  4. Prompt Engineering Guide - Técnicas de prompt engineering para mejores templates
  5. Conventional Commits - Estándar usado en el ejemplo de commit messages
  6. MCP Inspector - Herramienta para probar prompts interactivamente