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

Mini-Proyecto: MCP Server con 1 Resource, 1 Tool, 1 Prompt

Mini-Proyecto: MCP Server con 1 Resource, 1 Tool, 1 Prompt

Descripción de la cápsula

Este es el momento. Has aprendido qué son Resources, Tools y Prompts. Has visto cómo se combinan. Ahora vas a construir tu primer MCP server funcional con las 3 primitivas trabajando juntas.

El server que vas a construir es deliberadamente mínimo: 1 resource, 1 tool, y 1 prompt. No es un server de producción — es tu primer prototipo, la semilla que escalarás en los módulos 4-8. El objetivo es que experimentes el ciclo completo: crear un server, registrar primitivas, conectarlo a Claude Code, y verificar que funciona.

Cuando termines esta cápsula, habrás pasado de "entiendo las primitivas de MCP" a "construí un MCP server que funciona." Ese es un milestone importante.


¿Qué vamos a construir?

El server: DevFiles

Un MCP server llamado devfiles-server que ayuda a gestionar archivos de desarrollo:

devfiles-server
├── Resource: project-structure
│   → Lista los archivos y carpetas de un directorio de proyecto
│   → URI: files://project/structure
│
├── Tool: create_file
│   → Crea un nuevo archivo con contenido especificado
│   → Incluye validación y manejo de errores
│
└── Prompt: refactoring-plan
    → Template para pedir un plan de refactoring de un archivo
    → Incluye criterios y formato estandarizado

Por qué estas 3 primitivas

  • Resource (project-structure): Es lo más básico que un developer necesita — ver qué archivos tiene. El modelo necesita este contexto para ayudar.
  • Tool (create_file): Crea archivos nuevos — la acción más simple con side effects reales. Puedes verificar que funcionó mirando tu filesystem.
  • Prompt (refactoring-plan): Estandariza cómo pedir un refactoring — algo que developers hacen frecuentemente y donde la calidad del prompt importa.

Prerequisitos

Antes de empezar, verifica:

# Node.js v18+
node --version

# npm
npm --version

# npx
npx --version

# Claude Code
claude --version

Si algo falta, instálalo antes de continuar. No dejes el setup para después.


Paso 1: Crear el proyecto

Estructura de archivos

# Crear el directorio del proyecto
mkdir devfiles-server
cd devfiles-server

# Inicializar el proyecto
npm init -y

Instalar dependencias

# SDK de MCP para TypeScript
npm install @modelcontextprotocol/sdk

# Zod para validación de schemas
npm install zod

# TypeScript y types de Node
npm install -D typescript @types/node

Configurar TypeScript

Crea el archivo tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

Configurar package.json

Actualiza tu package.json:

{
  "name": "devfiles-server",
  "version": "1.0.0",
  "description": "MCP server mínimo con 1 resource, 1 tool, 1 prompt",
  "type": "module",
  "main": "build/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js",
    "dev": "tsc && node build/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "typescript": "^5.3.0",
    "@types/node": "^20.0.0"
  }
}

Crear la estructura de código

mkdir src

Tu proyecto debería verse así:

devfiles-server/
├── package.json
├── tsconfig.json
├── node_modules/
└── src/
    └── index.ts    ← aquí irá todo el código

Paso 2: Implementar el server

Crea el archivo src/index.ts con el código completo del server. Vamos sección por sección.

2.1: Imports y setup del server

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";

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

Qué hace cada import:

  • McpServer — La clase principal del SDK para crear un MCP server
  • StdioServerTransport — Transport que usa stdin/stdout para comunicación local
  • z — Zod para definir y validar schemas de inputs
  • fs/promises — API de filesystem asíncrono de Node.js
  • path — Utilidades para manejar rutas de archivos

2.2: Resource — Estructura del proyecto

const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();

server.resource(
  "project-structure",
  "files://project/structure",
  {
    description: "Estructura de archivos y carpetas del proyecto actual",
    mimeType: "application/json",
  },
  async (uri) => {
    async function getStructure(dir: string, prefix: string = ""): Promise<string[]> {
      const entries = await fs.readdir(dir, { withFileTypes: true });
      const result: string[] = [];

      const filtered = entries.filter(
        (e) => !e.name.startsWith(".") && e.name !== "node_modules"
      );

      for (const entry of filtered) {
        const fullPath = path.join(dir, entry.name);
        const relativePath = path.relative(PROJECT_DIR, fullPath);

        if (entry.isDirectory()) {
          result.push(`📁 ${prefix}${entry.name}/`);
          const children = await getStructure(fullPath, prefix + "  ");
          result.push(...children);
        } else {
          const stats = await fs.stat(fullPath);
          const sizeKB = (stats.size / 1024).toFixed(1);
          result.push(`📄 ${prefix}${entry.name} (${sizeKB} KB)`);
        }
      }

      return result;
    }

    try {
      const structure = await getStructure(PROJECT_DIR);

      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(
              {
                projectDir: PROJECT_DIR,
                totalItems: structure.length,
                structure: structure,
              },
              null,
              2
            ),
          },
        ],
      };
    } catch (error) {
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(
              {
                error: `No se pudo leer ${PROJECT_DIR}: ${error instanceof Error ? error.message : "error desconocido"}`,
              },
              null,
              2
            ),
          },
        ],
      };
    }
  }
);

Puntos clave:

  • El resource lee la estructura del directorio recursivamente
  • Filtra archivos ocultos (.git, .env) y node_modules
  • Muestra el tamaño de cada archivo
  • Maneja errores graciosamente (no crash si el directorio no existe)

2.3: Tool — Crear archivo

server.tool(
  "create_file",
  "Crea un nuevo archivo en el proyecto con el contenido especificado. Crea directorios intermedios si no existen.",
  {
    filePath: z
      .string()
      .describe(
        "Ruta relativa del archivo a crear (e.g., 'src/utils/helpers.ts')"
      ),
    content: z.string().describe("Contenido completo del archivo"),
    overwrite: z
      .boolean()
      .default(false)
      .describe(
        "Si true, sobreescribe el archivo si ya existe. Default: false"
      ),
  },
  async ({ filePath, content, overwrite }) => {
    // Validación de seguridad
    if (filePath.includes("..")) {
      return {
        content: [
          {
            type: "text" as const,
            text: "❌ Error de seguridad: la ruta no puede contener '..' (path traversal)",
          },
        ],
        isError: true,
      };
    }

    const absolutePath = path.resolve(PROJECT_DIR, filePath);

    // Verificar que la ruta está dentro del proyecto
    if (!absolutePath.startsWith(PROJECT_DIR)) {
      return {
        content: [
          {
            type: "text" as const,
            text: "❌ Error de seguridad: la ruta debe estar dentro del directorio del proyecto",
          },
        ],
        isError: true,
      };
    }

    // Verificar si el archivo ya existe
    if (!overwrite) {
      try {
        await fs.access(absolutePath);
        return {
          content: [
            {
              type: "text" as const,
              text: `❌ El archivo '${filePath}' ya existe. Usa overwrite: true para sobreescribirlo.`,
            },
          ],
          isError: true,
        };
      } catch {
        // El archivo no existe — podemos continuar
      }
    }

    try {
      // Crear directorios intermedios
      const dir = path.dirname(absolutePath);
      await fs.mkdir(dir, { recursive: true });

      // Escribir el archivo
      await fs.writeFile(absolutePath, content, "utf-8");

      // Verificar que se creó correctamente
      const stats = await fs.stat(absolutePath);

      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(
              {
                success: true,
                message: `✅ Archivo creado: ${filePath}`,
                details: {
                  path: filePath,
                  absolutePath,
                  size: `${(stats.size / 1024).toFixed(1)} KB`,
                  characters: content.length,
                  lines: content.split("\n").length,
                },
              },
              null,
              2
            ),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text" as const,
            text: `❌ Error al crear archivo: ${error instanceof Error ? error.message : "error desconocido"}`,
          },
        ],
        isError: true,
      };
    }
  }
);

Puntos clave:

  • Validación de seguridad: previene path traversal con ..
  • Verifica que la ruta está dentro del directorio del proyecto
  • Crea directorios intermedios automáticamente
  • Comportamiento explícito con overwrite (no sobreescribe por default)
  • Retorna detalles del archivo creado: tamaño, líneas, characters

2.4: Prompt — Plan de refactoring

server.prompt(
  "refactoring-plan",
  "Genera un plan de refactoring detallado para un archivo del proyecto",
  {
    filePath: z
      .string()
      .describe(
        "Ruta del archivo a refactorizar (e.g., 'src/index.ts')"
      ),
    goal: z
      .string()
      .default("mejorar legibilidad y mantenibilidad")
      .describe(
        "Objetivo del refactoring (e.g., 'separar responsabilidades', 'mejorar performance')"
      ),
    level: z
      .enum(["conservative", "moderate", "aggressive"])
      .default("moderate")
      .describe(
        "Nivel de refactoring: conservative (mínimos cambios), moderate (balance), aggressive (reestructuración completa)"
      ),
  },
  async ({ filePath, goal, level }) => {
    const absolutePath = path.resolve(PROJECT_DIR, filePath);
    let fileContent: string;

    try {
      fileContent = await fs.readFile(absolutePath, "utf-8");
    } catch {
      fileContent = `[No se pudo leer el archivo: ${filePath}. Verifica que existe.]`;
    }

    const levelDescription = {
      conservative:
        "Aplica cambios mínimos: renaming, extracción de constantes, limpieza de código muerto. No cambia la estructura general.",
      moderate:
        "Balance entre mejora y estabilidad: extrae funciones, separa responsabilidades, mejora tipos. Cambia estructura interna pero mantiene la API.",
      aggressive:
        "Reestructuración completa si es necesario: cambia patrones de diseño, separa en módulos, reescribe secciones. Puede cambiar la API.",
    };

    const lineCount = fileContent.split("\n").length;

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

**Archivo:** ${filePath} (${lineCount} líneas)
**Objetivo:** ${goal}
**Nivel:** ${level} — ${levelDescription[level]}

**El plan debe incluir:**

### 1. Análisis del estado actual
- Qué hace el archivo (propósito principal)
- Problemas identificados (code smells, complejidad, duplicación)
- Métricas: funciones/clases, líneas por función, nivel de anidación

### 2. Cambios propuestos
Para cada cambio:
- **Qué:** descripción del cambio
- **Por qué:** qué problema resuelve
- **Impacto:** alto/medio/bajo
- **Riesgo:** qué podría romperse

### 3. Orden de ejecución
- Lista ordenada de pasos (hacer primero lo de menor riesgo)
- Dependencias entre pasos

### 4. Código sugerido
- Snippets de cómo quedaría después del refactoring
- Antes/después para los cambios principales

### 5. Tests necesarios
- Qué tests agregar ANTES de refactorizar (safety net)
- Qué tests agregar DESPUÉS (verificar nuevo comportamiento)

### 6. Estimación
- Tiempo estimado para cada paso
- Tiempo total

Usa el tool create_file si necesitas crear archivos nuevos como parte del plan.`,
          },
        },
      ],
    };
  }
);

Puntos clave:

  • Lee el contenido del archivo automáticamente
  • Lo incluye como resource embebido en el prompt
  • Tres niveles de refactoring para diferentes necesidades
  • Formato de output detallado y consistente
  • Conecta con el tool create_file para materializar el plan

2.5: Conectar el transport e iniciar el server

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("DevFiles MCP Server running on stdio");
  console.error(`Project directory: ${PROJECT_DIR}`);
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

Nota: Usamos console.error (no console.log) porque stdout está reservado para la comunicación MCP vía stdio. Los logs van a stderr.


Paso 3: Compilar y verificar

Compilar el proyecto

npm run build

Deberías ver la carpeta build/ con el archivo index.js compilado.

Verificar que compila sin errores

# Si hay errores de TypeScript, corrígelos antes de continuar
# Los errores más comunes:
# - Import paths sin extensión .js
# - Tipos faltantes
# - strict mode violations

Verificar que el server arranca

# Ejecutar directamente (debería imprimir a stderr y esperar input en stdin)
node build/index.js

# Si ves "DevFiles MCP Server running on stdio", funciona
# Presiona Ctrl+C para salir

Paso 4: Probar con MCP Inspector

Antes de conectar a Claude Code, prueba con MCP Inspector — una herramienta visual para interactuar con MCP servers:

# Ejecutar MCP Inspector apuntando a tu server
npx @modelcontextprotocol/inspector node build/index.js

En el Inspector:

  1. Resources tab — Deberías ver project-structure. Haz click para leerlo y verificar que retorna la estructura de archivos.

  2. Tools tab — Deberías ver create_file. Prueba con:

    {
      "filePath": "test-file.txt",
      "content": "Hola desde MCP Inspector!"
    }

    Verifica que el archivo se creó en tu filesystem.

  3. Prompts tab — Deberías ver refactoring-plan. Prueba con:

    {
      "filePath": "src/index.ts",
      "goal": "mejorar legibilidad",
      "level": "conservative"
    }

    Verifica que genera el template completo con el contenido del archivo.

Troubleshooting del Inspector

"No se puede conectar al server":

# Verifica que el build existe
ls build/index.js

# Verifica que el server arranca manualmente
node build/index.js
# Debería imprimir a stderr y esperar

"Resource retorna error":

# Verifica que el directorio de proyecto existe
echo $PROJECT_DIR
ls $(pwd)

# Asegúrate de ejecutar desde el directorio correcto
cd /tu/directorio/de/proyecto
npx @modelcontextprotocol/inspector node /ruta/a/devfiles-server/build/index.js

"Tool create_file falla":

# Verifica permisos de escritura
touch test-perms.txt && rm test-perms.txt
# Si falla, hay un problema de permisos en el directorio

Paso 5: Conectar a Claude Code

Agregar el server a Claude Code

# Desde el directorio de tu server:
claude mcp add devfiles -s user -- node /ruta/absoluta/a/devfiles-server/build/index.js

Importante: Usa la ruta absoluta al archivo index.js. Para obtenerla:

# Desde el directorio del server
echo "$(pwd)/build/index.js"

Verificar la conexión

# Abrir Claude Code
claude

# Verificar que el server está conectado
/mcp

Deberías ver:

MCP Servers:
  devfiles: connected
    Tools:
      - create_file
    Resources:
      - project-structure (files://project/structure)
    Prompts:
      - refactoring-plan

Probar cada primitiva en Claude Code

Probar el Resource:

Tú: "¿Qué archivos hay en mi proyecto?"

Claude Code debería usar el resource project-structure y mostrarte la estructura.

Probar el Tool:

Tú: "Crea un archivo llamado src/utils/constants.ts con constantes básicas del proyecto"

Claude Code debería usar create_file y crear el archivo. Verifica que existe:

cat src/utils/constants.ts

Probar el Prompt:

Tú: Usa el prompt refactoring-plan para analizar src/index.ts con nivel moderate

Claude Code debería generar un plan de refactoring detallado usando el template.


Paso 6: Experimentar

Ahora que tu server funciona, experimenta:

Experiment 1: Flujo combinado

Pide a Claude Code que use las 3 primitivas en secuencia:

"Primero muéstrame la estructura del proyecto.
Luego crea un archivo src/README.md con documentación básica.
Finalmente, usa el prompt de refactoring para analizar el index.ts."

Observa cómo Claude Code orquesta las 3 primitivas automáticamente.

Experiment 2: Configurar PROJECT_DIR

Puedes apuntar tu server a cualquier directorio:

# Remover la configuración actual
claude mcp remove devfiles

# Re-agregar apuntando a otro directorio
PROJECT_DIR=/ruta/a/otro/proyecto claude mcp add devfiles -s user -- node /ruta/a/devfiles-server/build/index.js

Experiment 3: Agregar un segundo resource

Agrega un resource que lea el package.json del proyecto. Verás esto como ejercicio más abajo — inténtalo por tu cuenta antes de ver la solución. Recompila (npm run build) y reinicia Claude Code para que detecte el cambio.


Implementación alternativa en Python

Si prefieres Python, el setup es más simple. El SDK de Python usa decoradores en lugar de métodos:

mkdir devfiles-server-py && cd devfiles-server-py
python -m venv venv && source venv/bin/activate
pip install mcp

La estructura del código sigue el mismo patrón — @server.resource() para resources, @server.tool() para tools, @server.prompt() para prompts. Puedes ver ejemplos completos en las cápsulas 02-04 de este módulo, donde cada primitiva incluye implementaciones en ambos lenguajes.

# Conectar a Claude Code
claude mcp add devfiles -s user -- python /ruta/absoluta/a/server.py

Verificación final

Checklist de éxito

Antes de dar por terminado el mini-proyecto, verifica:

  • El server compila sin errores (npm run build sin warnings)
  • El server arranca (imprime a stderr y espera input)
  • MCP Inspector muestra las 3 primitivas (Resources, Tools, Prompts tabs)
  • El Resource retorna datos (estructura de archivos en JSON)
  • El Tool crea archivos (verifica en tu filesystem)
  • El Tool maneja errores (prueba con archivo existente y overwrite: false)
  • El Prompt genera el template (con el contenido del archivo incluido)
  • Claude Code conecta (/mcp muestra devfiles: connected)
  • Las 3 primitivas funcionan en Claude Code (prueba cada una)

Si algo del checklist falla, revisa la sección de Troubleshooting a continuación.


Troubleshooting

"Error: Cannot find module @modelcontextprotocol/sdk"

Causa: Las dependencias no se instalaron correctamente.

Solución:

rm -rf node_modules package-lock.json
npm install
npm run build

"Server arranca pero Claude Code muestra 0 tools"

Causa: El server se registra pero las primitivas no se declaran antes del connect().

Solución: Verifica que todas las llamadas a server.resource(), server.tool(), y server.prompt() están antes de server.connect(transport) en el código.

"Error: EPERM operation not permitted"

Causa: El proceso no tiene permisos para escribir en el directorio.

Solución:

# macOS: verificar permisos de la terminal
# System Preferences → Privacy & Security → Files and Folders

# Linux: verificar permisos del directorio
chmod 755 /tu/directorio

"El prompt no incluye el contenido del archivo"

Causa: El path del archivo es relativo y no se resuelve correctamente.

Solución: Verifica que PROJECT_DIR está configurado correctamente y que el archivo existe en esa ruta:

ls -la $(pwd)/src/index.ts

"Inspector funciona pero Claude Code no"

Causa: Diferencia en cómo se invoca el server.

Solución:

# Prueba exactamente el mismo comando que Claude Code ejecutará:
node /ruta/absoluta/build/index.js

# Si falla, el problema es la ruta o permisos
# Si funciona, re-agrega a Claude Code con la misma ruta exacta

Ejercicios de extensión

Ejercicio 1: Agregar un resource de package.json (Fácil)

Agrega un segundo resource que retorne el contenido del package.json del proyecto. Verifica que aparece en MCP Inspector y Claude Code.

Ver solución
server.resource(
  "package-info",
  "files://project/package",
  {
    description: "Contenido del package.json del proyecto",
    mimeType: "application/json",
  },
  async (uri) => {
    try {
      const content = await fs.readFile(
        path.join(PROJECT_DIR, "package.json"),
        "utf-8"
      );
      return {
        contents: [{
          uri: uri.href,
          mimeType: "application/json",
          text: content,
        }],
      };
    } catch {
      return {
        contents: [{
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify({ error: "package.json no encontrado" }),
        }],
      };
    }
  }
);

Agrega este código después del resource existente, recompila con npm run build, y reinicia Claude Code.

Ejercicio 2: Agregar un tool de búsqueda (Medio)

Agrega un tool search_in_files que busque un texto en todos los archivos del proyecto y retorne las coincidencias con número de línea.

Ver solución
server.tool(
  "search_in_files",
  "Busca un texto en todos los archivos del proyecto y retorna las coincidencias",
  {
    query: z.string().describe("Texto a buscar"),
    extension: z.string().optional().describe("Filtrar por extensión (e.g., '.ts', '.py')"),
  },
  async ({ query, extension }) => {
    const results: Array<{ file: string; line: number; text: string }> = [];

    async function searchDir(dir: string): Promise<void> {
      const entries = await fs.readdir(dir, { withFileTypes: true });

      for (const entry of entries) {
        if (entry.name.startsWith(".") || entry.name === "node_modules") continue;

        const fullPath = path.join(dir, entry.name);

        if (entry.isDirectory()) {
          await searchDir(fullPath);
        } else {
          if (extension && !entry.name.endsWith(extension)) continue;

          try {
            const content = await fs.readFile(fullPath, "utf-8");
            const lines = content.split("\n");

            lines.forEach((line, idx) => {
              if (line.includes(query)) {
                results.push({
                  file: path.relative(PROJECT_DIR, fullPath),
                  line: idx + 1,
                  text: line.trim(),
                });
              }
            });
          } catch {
            // Saltar archivos binarios o sin permisos
          }
        }
      }
    }

    await searchDir(PROJECT_DIR);

    return {
      content: [{
        type: "text" as const,
        text: JSON.stringify({
          query,
          totalMatches: results.length,
          results: results.slice(0, 50),
          truncated: results.length > 50,
        }, null, 2),
      }],
    };
  }
);

Ejercicio 3: Conectar con otro directorio (Medio)

Configura tu server para que funcione con 2 proyectos diferentes simultáneamente en Claude Code. Pista: puedes agregar el mismo server con diferentes nombres y diferentes PROJECT_DIR.

Ver solución
# Server para proyecto 1
claude mcp add devfiles-frontend -s user -- \
  sh -c "PROJECT_DIR=/ruta/a/frontend node /ruta/a/devfiles-server/build/index.js"

# Server para proyecto 2
claude mcp add devfiles-backend -s user -- \
  sh -c "PROJECT_DIR=/ruta/a/backend node /ruta/a/devfiles-server/build/index.js"

# Verificar
claude
/mcp

# Deberías ver:
# devfiles-frontend: connected
# devfiles-backend: connected

# Ahora puedes pedir:
# "Muéstrame la estructura del frontend" → usa devfiles-frontend
# "Crea un archivo en el backend" → usa devfiles-backend

El truco es usar sh -c para pasar la variable de entorno PROJECT_DIR al server. Cada instancia del server opera sobre su propio directorio.

Ejercicio 5: Implementar en Python (Difícil)

Replica el server devfiles-server completo en Python usando FastMCP. Usa @server.resource() para la estructura del proyecto, @server.tool() para create_file con las mismas validaciones de seguridad, y @server.prompt() para el plan de refactoring. Conéctalo a Claude Code y verifica que funciona.

Ver solución

Usa los ejemplos de Python de las cápsulas 02-04 como base. La estructura general:

from mcp.server.fastmcp import FastMCP
import os, json
from pathlib import Path

PROJECT_DIR = os.environ.get("PROJECT_DIR", os.getcwd())
server = FastMCP("devfiles-server")

@server.resource("files://project/structure")
async def project_structure() -> str:
    # Escanear directorio recursivamente, filtrar .git y node_modules
    ...

@server.tool()
async def create_file(file_path: str, content: str, overwrite: bool = False) -> str:
    # Validar path traversal, verificar existencia, crear dirs, escribir
    ...

@server.prompt()
async def refactoring_plan(file_path: str, goal: str = "mejorar legibilidad") -> str:
    # Leer archivo, generar template con instrucciones detalladas
    ...

if __name__ == "__main__":
    server.run()

Conéctalo: claude mcp add devfiles-py -s user -- python /ruta/a/server.py


Qué sigue

Resumen de lo que lograste

En este mini-proyecto:

  • ✅ Creaste un proyecto MCP desde cero con TypeScript y el SDK oficial
  • ✅ Implementaste las 3 primitivas: Resource, Tool, y Prompt
  • ✅ Compilaste y verificaste que el server funciona
  • ✅ Probaste con MCP Inspector (testing visual)
  • ✅ Conectaste a Claude Code y verificaste las 3 primitivas
  • ✅ Experimentaste con flujos combinados

Esto es un milestone. Construiste tu primer MCP server funcional. Ya no eres solo un usuario de MCP — eres un creador.

Conexión con los módulos siguientes

Lo que hiciste (Módulo 3):
  → Server mínimo: 1 resource, 1 tool, 1 prompt
  → Validación básica
  → Transport stdio
  → In-memory data

Lo que viene en Módulo 4 (MCP Server en TypeScript):
  → Server completo: múltiples resources, tools, prompts
  → Zod schemas avanzados con validación completa
  → Error handling robusto
  → Transports: stdio + HTTP/SSE
  → Conexión con APIs y databases reales

Lo que viene en Módulo 8 (Proyecto Final):
  → Server production-ready
  → Testing completo
  → Documentación profesional
  → Deploy y distribución

El server mínimo de este módulo es la semilla. En el módulo 4, lo expandirás con TypeScript avanzado. En el módulo 8, lo convertirás en un server que podrías publicar.


Resumen

En esta cápsula:

  • Construiste tu primer MCP server (devfiles-server) con las 3 primitivas
  • Resource (project-structure): expone la estructura de archivos del proyecto
  • Tool (create_file): crea archivos con validación de seguridad
  • Prompt (refactoring-plan): genera planes de refactoring con contexto del archivo
  • Probaste con MCP Inspector y Claude Code
  • Verificaste que las primitivas funcionan juntas en un flujo real

Próximo módulo: MCP Server en TypeScript — escala tu server mínimo a un server completo con múltiples tools tipados, Zod schemas avanzados, y transports reales.


Recursos adicionales

  1. MCP TypeScript SDK — Getting Started - Guía oficial de inicio rápido
  2. MCP Python SDK — Getting Started - Alternativa en Python
  3. MCP Inspector - Herramienta de testing visual
  4. Claude Code MCP Configuration - Cómo configurar servers en Claude Code
  5. Zod Documentation - Validación de schemas usada en el SDK
  6. MCP Servers Examples - Servers oficiales como referencia
  7. Awesome MCP Servers - Directorio de servers de la comunidad
  8. MCP Specification - Referencia técnica completa del protocolo