Módulo 2: Arquitectura Host-Client-Server
El Server: El Proveedor de Capabilities
El Server: El Proveedor de Capabilities
Descripción de la cápsula
Llegaste a la capa que más te importa como developer: el MCP Server. El Host orquesta, el Client comunica, pero el Server es tu código — el programa que tú escribes, que expone capabilities (tools, resources, prompts), responde a requests, y ejecuta operaciones reales. Cuando en el módulo 4 construyas tu primer MCP server en TypeScript, todo lo que aprendas en esta cápsula será tu mapa.
En esta cápsula vas a entender cómo un Server se estructura internamente, qué debe hacer durante la inicialización, cómo responde a requests del Client, cómo maneja su estado, y qué patrones de diseño producen Servers robustos. No vas a escribir código todavía (eso viene en módulos 4 y 5), pero vas a entender la anatomía completa de un Server.
Volviendo a la analogía del restaurante: el Server es la cocina. Tiene su chef (tu lógica de negocio), su menú (capabilities registradas), y su forma de preparar platos (handlers de requests). El mesero (Client) trae pedidos; la cocina los prepara y retorna el resultado.
Anatomía de un MCP Server
Estructura general
Todo MCP Server tiene estos componentes:
MCP Server
│
├── Registro de capabilities
│ ├── Tools: funciones que el modelo puede ejecutar
│ ├── Resources: datos que el modelo puede leer
│ └── Prompts: templates reutilizables
│
├── Handlers
│ ├── initialize handler → Responde al handshake del Client
│ ├── tools/list handler → Retorna lista de tools disponibles
│ ├── tools/call handler → Ejecuta un tool específico
│ ├── resources/list handler → Retorna lista de resources
│ ├── resources/read handler → Lee un resource específico
│ ├── prompts/list handler → Retorna lista de prompts
│ └── prompts/get handler → Retorna un prompt específico
│
├── Lógica de negocio
│ ├── Conexiones a APIs externas
│ ├── Acceso a databases
│ ├── Operaciones con archivos
│ └── Cualquier operación que tu Server provea
│
└── Transport
├── stdio (lee de stdin, escribe en stdout)
└── HTTP/SSE (escucha en un puerto)
Ciclo de vida del Server
Desde que el proceso arranca hasta que termina:
1. STARTUP
└── El proceso inicia (lanzado por el Host)
├── Configura el transport (stdio o HTTP)
└── Registra capabilities (tools, resources, prompts)
2. INITIALIZE (responde al Client)
└── Recibe "initialize" del Client
├── Retorna capabilities y serverInfo
└── Recibe "notifications/initialized"
3. READY (listo para operar)
└── Escucha requests del Client
├── tools/list → Retorna lista
├── tools/call → Ejecuta y retorna resultado
├── resources/list → Retorna lista
├── resources/read → Lee y retorna datos
├── prompts/list → Retorna lista
└── prompts/get → Retorna prompt
4. SHUTDOWN
└── El Client cierra la conexión
└── El proceso termina
Capabilities: qué puede exponer un Server
Las 3 primitivas
Un MCP Server puede exponer hasta 3 tipos de capabilities. Cada una tiene un propósito distinto:
Capabilities del Server:
│
├── 🔧 Tools (funciones ejecutables)
│ ├── El modelo las invoca activamente
│ ├── Pueden tener side effects (escribir, crear, modificar)
│ ├── Cada tool tiene input schema (parámetros validados)
│ └── Ejemplo: create_file, run_query, send_message
│
├── 📄 Resources (datos de lectura)
│ ├── El modelo las lee para obtener contexto
│ ├── Son read-only (no modifican nada)
│ ├── Identificadas por URI (protocol://path)
│ └── Ejemplo: db://schema, config://settings
│
└── 💬 Prompts (templates)
├── Templates predefinidos con parámetros
├── Ayudan al modelo a hacer preguntas específicas
├── El usuario las selecciona explícitamente
└── Ejemplo: analyze_table(name), review_code(file)
Importante: No todo Server necesita exponer las 3. Un Server mínimo puede tener solo 1 tool. Un Server completo puede tener docenas de tools, resources, y prompts.
En esta cápsula verás los 3 tipos a nivel arquitectural. El módulo 3 profundizará en cada primitiva con detalle completo.
Ejemplo: Server de database
Veamos cómo se traducen las 3 primitivas en un Server concreto:
Database MCP Server:
│
├── 🔧 Tools:
│ ├── query(sql) → Ejecuta una query SQL
│ ├── list_tables() → Lista tablas de la database
│ ├── describe_table(name) → Schema de una tabla
│ └── insert_record(table, data) → Inserta un registro
│
├── 📄 Resources:
│ ├── db://schema → Schema completo de la database
│ ├── db://tables/users → Datos de la tabla users
│ └── db://stats → Estadísticas de la database
│
└── 💬 Prompts:
├── analyze_table(name) → "Analiza la estructura y
│ datos de la tabla {name} y sugiere mejoras"
└── optimize_query(sql) → "Revisa esta query SQL
y sugiere optimizaciones: {sql}"
Cómo un Server registra capabilities
El proceso de registro
Cuando escribes un MCP Server, el primer paso es registrar qué capabilities expone. Cada SDK (TypeScript, Python) tiene su forma de hacerlo, pero conceptualmente es lo mismo:
Registro de un tool:
├── Nombre: "read_file"
├── Descripción: "Lee el contenido de un archivo"
├── Input Schema:
│ ├── path (string, required): "Ruta al archivo"
│ └── encoding (string, optional): "Codificación del archivo"
└── Handler: función que se ejecuta cuando el tool es invocado
En pseudocódigo (la sintaxis real la verás en módulos 4 y 5):
// Pseudocódigo - registrar un tool
server.register_tool({
name: "read_file",
description: "Lee el contenido de un archivo",
input_schema: {
path: { type: "string", required: true },
encoding: { type: "string", required: false, default: "utf-8" }
},
handler: function(args) {
content = read_from_disk(args.path, args.encoding)
return { type: "text", text: content }
}
})
// Pseudocódigo - registrar un resource
server.register_resource({
uri: "config://app-settings",
name: "Application Settings",
description: "Current application configuration",
mime_type: "application/json",
handler: function() {
settings = load_settings()
return { type: "text", text: JSON.stringify(settings) }
}
})
// Pseudocódigo - registrar un prompt
server.register_prompt({
name: "analyze_logs",
description: "Analyze application logs for errors",
arguments: [
{ name: "timeframe", description: "Time period to analyze", required: true }
],
handler: function(args) {
return {
messages: [
{
role: "user",
content: "Analyze the application logs from the last " + args.timeframe +
". Focus on errors, warnings, and unusual patterns."
}
]
}
}
})
Input Schema: la importancia de la validación
Cada tool define un inputSchema usando JSON Schema. Esto no es decorativo — el Client usa estos schemas para:
- Informar al modelo qué parámetros puede pasar
- Validar que los argumentos sean correctos antes de enviarlos
- Documentar la API del tool automáticamente
{
"name": "create_user",
"description": "Crea un nuevo usuario en la database",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Nombre completo del usuario"
},
"email": {
"type": "string",
"format": "email",
"description": "Email del usuario (debe ser único)"
},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"],
"description": "Rol del usuario en el sistema"
}
},
"required": ["name", "email"]
}
}
El modelo (Claude) ve este schema y sabe:
nameyemailson obligatoriosroletiene 3 valores válidosemaildebe tener formato de email
Esto permite que Claude construya argumentos correctos sin que el usuario tenga que especificar cada campo.
Cómo un Server responde a requests
Formato de respuesta
Todo response de un tool tiene la misma estructura:
{
"content": [
{
"type": "text",
"text": "El resultado de la operación..."
}
],
"isError": false
}
El campo content es un array que puede contener diferentes tipos:
Tipos de content:
│
├── text
│ { "type": "text", "text": "contenido textual" }
│ Uso: la mayoría de respuestas
│
├── image
│ { "type": "image", "data": "base64...", "mimeType": "image/png" }
│ Uso: capturas de pantalla, gráficos
│
└── resource
{ "type": "resource", "resource": { "uri": "...", "text": "..." } }
Uso: referenciar un resource
Respuesta exitosa
{
"content": [
{
"type": "text",
"text": "Archivo creado exitosamente: /Users/dev/output.json\nTamaño: 1.2 KB"
}
],
"isError": false
}
Respuesta con error
{
"content": [
{
"type": "text",
"text": "Error: No se pudo conectar a la database. Verifica que PostgreSQL esté corriendo y las credenciales sean correctas.\n\nDetalles: Connection refused at localhost:5432"
}
],
"isError": true
}
El Server retorna errores de operación dentro de content con isError: true. Esto permite que el Host presente el error al usuario de forma legible.
Manejo de estado en el Server
Stateless vs Stateful
Los MCP Servers pueden ser stateless o stateful. La elección depende del caso de uso:
Stateless Server:
├── Cada request es independiente
├── No mantiene información entre requests
├── Ejemplo: Filesystem server (lee/escribe archivos, sin memoria)
├── Ventaja: Simple, predecible
└── Cuándo usarlo: operaciones atómicas
Stateful Server:
├── Mantiene estado entre requests
├── Requests anteriores afectan los siguientes
├── Ejemplo: Database server (mantiene conexión abierta)
├── Ventaja: Eficiente (no reconecta cada vez)
└── Cuándo usarlo: conexiones persistentes, caches
Ejemplos de estado
Estado que un Server puede mantener:
│
├── Conexión a database
│ └── Abrir conexión al iniciar, reutilizar en cada request
│
├── Cache de resultados
│ └── Cachear queries frecuentes para responder más rápido
│
├── Sesión de autenticación
│ └── Token de API renovable, no re-autenticar cada request
│
├── Configuración cargada
│ └── Leer config al iniciar, no releer cada request
│
└── Contadores / métricas
└── Trackear cuántas operaciones se han ejecutado
Estado y lifecycle
El estado del Server existe mientras el proceso viva. Cuando el Host cierra la conexión (shutdown), el proceso termina y el estado se pierde:
Lifecycle del estado:
│
├── STARTUP → Estado inicial (vacío o desde config)
├── INITIALIZE → Puede cargar estado persistente
├── OPERATION → Estado se acumula (cache, conexiones)
└── SHUTDOWN → Estado se pierde (a menos que persistas)
Si necesitas estado persistente entre sesiones, tu Server debe guardarlo explícitamente (en archivo, database, etc.).
Patrones de diseño para Servers
Patrón 1: Server de API wrapper
El patrón más común — tu Server envuelve una API existente:
API Wrapper Server:
│
├── Entrada: request MCP del Client
├── Proceso: traduce a API call
├── Salida: respuesta MCP al Client
│
│ Client → Server → API Externa
│ ← Response
│ ← MCP Response
│ ← Presenta al usuario
Ejemplo: GitHub MCP Server
├── tools/call: create_issue(repo, title, body)
│ └── Traduce a: POST https://api.github.com/repos/{repo}/issues
│ └── Retorna: { content: [{ type: "text", text: "Issue #42 created" }] }
Patrón 2: Server de database
El Server mantiene una conexión a la database y expone operaciones:
Database Server:
│
├── STARTUP: abre conexión a PostgreSQL
├── tools/call: query(sql)
│ └── Ejecuta: connection.query(sql)
│ └── Retorna: resultados formateados
├── resources/read: db://schema
│ └── Ejecuta: query information_schema
│ └── Retorna: schema de la database
└── SHUTDOWN: cierra conexión
Patrón 3: Server de filesystem
Opera directamente con el sistema de archivos:
Filesystem Server:
│
├── No mantiene conexiones (stateless)
├── tools/call: read_file(path)
│ └── Lee: fs.readFile(path)
│ └── Retorna: contenido del archivo
├── tools/call: write_file(path, content)
│ └── Escribe: fs.writeFile(path, content)
│ └── Retorna: confirmación
└── Validación: verifica que paths estén dentro de allowed directories
Patrón 4: Server de agregación
Combina múltiples fuentes de datos:
Aggregation Server:
│
├── tools/call: project_status()
│ ├── Lee: GitHub API → PRs abiertos
│ ├── Lee: Jira API → tickets activos
│ ├── Lee: CI/CD → último build
│ └── Retorna: resumen consolidado
Seguridad en el Server
Validación de inputs
Tu Server recibe argumentos del Client, pero no debes confiar ciegamente en ellos:
Validaciones esenciales:
│
├── Path traversal
│ ├── Input: path = "../../etc/passwd"
│ ├── Validación: verificar que path esté dentro del allowed directory
│ └── Sin validación: el Server lee archivos del sistema
│
├── SQL injection
│ ├── Input: sql = "'; DROP TABLE users; --"
│ ├── Validación: usar prepared statements
│ └── Sin validación: la database se corrompe
│
├── Size limits
│ ├── Input: content = (archivo de 10GB)
│ ├── Validación: limitar tamaño de input
│ └── Sin validación: el Server consume toda la memoria
│
└── Rate limiting
├── Input: 1000 requests por segundo
├── Validación: limitar requests
└── Sin validación: la API externa te bloquea
Principio de mínimo privilegio
Tu Server debe tener solo los permisos que necesita:
✅ Bien:
├── Filesystem server: solo puede leer/escribir en /Users/dev/projects
├── Database server: solo puede ejecutar SELECT (no DROP/ALTER)
└── GitHub server: solo tiene read access a repos públicos
❌ Mal:
├── Filesystem server: puede acceder a todo el disco
├── Database server: tiene permisos de admin
└── GitHub server: tiene full access a repos privados
Server vs Host vs Client: responsabilidades claras
¿Quién hace qué?
"El usuario quiere contar archivos .py"
Host (Claude Code):
├── ✅ Recibe la petición del usuario
├── ✅ Decide que necesita el Filesystem server
├── ✅ Decide usar el tool "search_files"
└── ✅ Presenta el resultado al usuario
Client:
├── ✅ Envía tools/call al Server
├── ✅ Recibe la respuesta JSON-RPC
└── ✅ Entrega el resultado al Host
Server:
├── ✅ Recibe el request de search_files
├── ✅ Busca archivos .py en el directorio
├── ✅ Formatea el resultado
└── ✅ Retorna la respuesta al Client
¿Quién NO debe hacer qué?
Server:
├── ❌ Decidir si el usuario puede ejecutar esta operación (eso es del Host)
├── ❌ Presentar resultados al usuario (eso es del Host)
└── ❌ Hablar con otros Servers (eso lo coordina el Host)
Troubleshooting
"Mi tool no aparece en Claude Code"
Causa: El tool no está registrado correctamente en el Server, o las capabilities no incluyen tools.
Solución: Verifica:
- Que tu Server retorne
"tools": {}en capabilities durante initialize - Que el tool esté registrado con nombre, descripción, e inputSchema
- Que
tools/listretorne el tool en la lista
// Verificar: la respuesta de tools/list debe incluir tu tool
{
"tools": [
{
"name": "my_tool",
"description": "Descripción del tool",
"inputSchema": { "type": "object", "properties": {} }
}
]
}
"El tool se ejecuta pero retorna vacío"
Causa: Tu handler retorna un formato incorrecto.
Solución: Verifica que retornes el formato correcto:
// ✅ Correcto
{
"content": [
{ "type": "text", "text": "resultado aquí" }
]
}
// ❌ Incorrecto (falta content array)
{
"text": "resultado aquí"
}
// ❌ Incorrecto (content no es array)
{
"content": { "type": "text", "text": "resultado" }
}
"Error: Tool execution failed"
Causa: Tu handler lanzó una excepción no manejada.
Solución: Siempre envuelve tu lógica en try/catch y retorna errores como isError: true:
// Pseudocódigo
handler(args):
try:
result = do_operation(args)
return { content: [{ type: "text", text: result }], isError: false }
catch error:
return { content: [{ type: "text", text: error.message }], isError: true }
"El Server crashea al iniciar"
Causa: Error en la configuración del transport o en la inicialización.
Solución:
# Ejecutar el server manualmente para ver errores
node ./my-server.js
# Los errores van a stderr
# Causas comunes:
# - Puerto ya en uso (HTTP transport)
# - Dependencias faltantes
# - Variables de entorno no configuradas
"Resources no se actualizan"
Causa: El Server cachea resources y no los refresca.
Solución: Implementa notifications de cambio:
// Cuando un resource cambia, el Server puede notificar al Client:
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "db://users"
}
}
Ejercicios
Ejercicio 1: Identificar capabilities de un Server (Fácil)
Para cada escenario, decide si necesitas un Tool, un Resource, o un Prompt:
- Mostrar la configuración actual de la aplicación
- Crear un nuevo usuario en la database
- Template para analizar logs de error
- Listar los endpoints de una API
- Enviar un mensaje a Slack
- Template para generar documentación de un endpoint
Ver solución
- Resource —
config://app-settings— Es datos de lectura, no una acción - Tool —
create_user(name, email)— Es una acción con side effects - Prompt —
analyze_logs(timeframe)— Es un template reutilizable - Resource —
api://endpoints— Es datos de lectura - Tool —
send_message(channel, text)— Es una acción con side effects - Prompt —
document_endpoint(path, method)— Es un template reutilizable
Regla rápida:
- ¿Modifica algo? → Tool
- ¿Solo lee datos? → Resource
- ¿Es un template para el modelo? → Prompt
Ejercicio 2: Diseñar el inputSchema de un tool (Medio)
Diseña el JSON Schema completo para un tool llamado send_email que acepta:
to(string, requerido): email del destinatariosubject(string, requerido): asunto del emailbody(string, requerido): contenido del emailcc(array de strings, opcional): emails en copiapriority(string, opcional): "low", "normal", o "high"
Ver solución
{
"name": "send_email",
"description": "Envía un email a un destinatario con asunto y cuerpo",
"inputSchema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"format": "email",
"description": "Email del destinatario"
},
"subject": {
"type": "string",
"description": "Asunto del email"
},
"body": {
"type": "string",
"description": "Contenido del email (texto plano o HTML)"
},
"cc": {
"type": "array",
"items": {
"type": "string",
"format": "email"
},
"description": "Lista de emails en copia (opcional)"
},
"priority": {
"type": "string",
"enum": ["low", "normal", "high"],
"default": "normal",
"description": "Prioridad del email"
}
},
"required": ["to", "subject", "body"]
}
}
Puntos clave:
requiredsolo incluye los 3 campos obligatoriosccusa"type": "array"con"items"que define el tipo de cada elementopriorityusa"enum"para limitar valores válidosformat: "email"ayuda al modelo a generar emails válidos
Ejercicio 3: Escribir respuestas de Server (Medio)
Escribe la respuesta JSON que un Server retornaría para cada escenario:
- El tool
count_recordsejecutó exitosamente y contó 1,247 usuarios - El tool
delete_filefalló porque el archivo no existe - El resource
db://schemaretorna el schema de la database
Ver solución
1. Éxito de count_records:
{
"content": [
{
"type": "text",
"text": "Total de registros en la tabla users: 1,247"
}
],
"isError": false
}
2. Error de delete_file:
{
"content": [
{
"type": "text",
"text": "Error: El archivo '/Users/dev/output.log' no existe. Verifica la ruta e intenta de nuevo."
}
],
"isError": true
}
3. Resource db://schema:
{
"contents": [
{
"uri": "db://schema",
"mimeType": "application/json",
"text": "{\n \"tables\": [\n {\"name\": \"users\", \"columns\": [\"id\", \"name\", \"email\"]},\n {\"name\": \"posts\", \"columns\": [\"id\", \"title\", \"user_id\"]}\n ]\n}"
}
]
}
Nota: Los resources usan contents (plural) con uri y mimeType. Los tools usan content (singular) con type y text.
Ejercicio 4: Diseñar un Server completo conceptualmente (Difícil)
Diseña un MCP Server para un sistema de notas personales. Define:
- Al menos 3 tools con sus inputSchemas
- Al menos 2 resources con sus URIs
- Al menos 1 prompt con sus argumentos
- Qué estado mantendría el Server
- Qué validaciones de seguridad implementarías
Ver solución
Tools:
-
create_note(title, content, tags)— Crea una nueva nota{ "inputSchema": { "type": "object", "properties": { "title": { "type": "string", "description": "Título de la nota" }, "content": { "type": "string", "description": "Contenido de la nota" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Tags para organizar" } }, "required": ["title", "content"] } } -
search_notes(query, tags)— Busca notas por contenido o tags{ "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Texto a buscar" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "Filtrar por tags" } }, "required": ["query"] } } -
delete_note(id)— Elimina una nota por ID{ "inputSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "ID de la nota a eliminar" } }, "required": ["id"] } }
Resources:
notes://all— Lista todas las notas (título, fecha, tags)notes://tags— Lista todos los tags existentes con conteo
Prompt:
summarize_notes(tag)— "Resume todas las notas con el tag {tag}. Identifica temas comunes y puntos clave."
Estado del Server:
- Conexión a SQLite local (almacena notas)
- Cache de tags (evita recalcular con cada request)
- Índice de búsqueda en memoria (para búsquedas rápidas)
Validaciones de seguridad:
- Sanitizar contenido de notas (prevenir script injection si se renderiza)
- Limitar tamaño de notas (máximo 100KB)
- Validar que IDs sean UUIDs válidos (prevenir inyección)
- Rate limiting en create_note (máximo 100 notas por minuto)
Ejercicio 5: Identificar patrones de Server (Difícil)
Para cada MCP Server descrito, identifica qué patrón de diseño usa (API wrapper, database, filesystem, o agregación) y justifica:
- Un Server que consulta la API de OpenWeather para dar pronósticos
- Un Server que lee y escribe archivos de configuración YAML
- Un Server que combina datos de GitHub, Jira, y Slack para dar un "project dashboard"
- Un Server que ejecuta queries en MongoDB
Ver solución
-
API Wrapper — Envuelve la API de OpenWeather. Traduce MCP requests a HTTP calls a la API, y formatea responses.
-
Filesystem — Opera directamente con archivos YAML en disco. Es stateless, cada operación lee/escribe independientemente.
-
Agregación — Combina múltiples fuentes (GitHub, Jira, Slack) en un resultado consolidado. Hace múltiples API calls internamente y presenta un dashboard unificado.
-
Database — Mantiene una conexión a MongoDB, ejecuta queries, y retorna resultados formateados. Es stateful (mantiene la conexión abierta).
Punto clave: Los patrones no son exclusivos — un Server puede combinar patrones. Por ejemplo, un Server de "project dashboard" (agregación) puede también cachear resultados en un archivo local (filesystem).
Ejercicio 6: Auditoría de seguridad de un Server (Difícil)
Dado este pseudocódigo de un MCP Server, identifica al menos 3 problemas de seguridad:
server.register_tool({
name: "run_query",
handler: function(args) {
result = database.execute(args.sql)
return { content: [{ type: "text", text: result }] }
}
})
server.register_tool({
name: "read_file",
handler: function(args) {
content = fs.readFile(args.path)
return { content: [{ type: "text", text: content }] }
}
})
Ver solución
Problema 1: SQL Injection
database.execute(args.sql)ejecuta cualquier SQL sin validación- Un atacante podría ejecutar
DROP TABLE usersoSELECT * FROM passwords - Fix: Usar prepared statements o limitar a queries SELECT
Problema 2: Path Traversal
fs.readFile(args.path)lee cualquier archivo del sistemaargs.path = "/etc/passwd"oargs.path = "../../secrets.env"serían válidos- Fix: Validar que
pathesté dentro de un directorio permitido
Problema 3: Sin manejo de errores
- Si
database.executeofs.readFilefallan, la excepción no está manejada - El Server crashea en vez de retornar un error limpio
- Fix: Envolver en try/catch, retornar
isError: true
Problema 4: Sin validación de input
- No hay inputSchema definido
- El Server acepta cualquier argumento sin validar tipo o formato
- Fix: Definir inputSchema con tipos y restricciones
Problema 5: Sin límites de tamaño
read_filepodría intentar leer un archivo de 10GBrun_querypodría retornar millones de registros- Fix: Limitar tamaño de archivos y resultados de queries
Resumen
En esta cápsula aprendiste:
- El MCP Server es tu código — el programa que expone capabilities al Host via el Client
- Expone 3 tipos de capabilities: Tools (funciones), Resources (datos), Prompts (templates)
- Cada tool tiene un inputSchema que valida parámetros y guía al modelo
- Las respuestas siguen un formato estándar con
contentarray yisErrorflag - Los Servers pueden ser stateless (filesystem) o stateful (database)
- Existen 4 patrones comunes: API wrapper, database, filesystem, agregación
- La seguridad es responsabilidad del Server: validar inputs, limitar acceso, manejar errores
- El Server no decide qué requests atender — eso lo decide el Host
Próxima cápsula: Flujo completo request-response — unir las 3 capas en un flujo end-to-end trazando un request completo + mini-proyecto: diagramar la arquitectura de tu setup.
Recursos adicionales
- MCP Server Development Guide - Documentación oficial sobre MCP Servers
- MCP Tools Specification - Especificación de Tools
- MCP Resources Specification - Especificación de Resources
- MCP Prompts Specification - Especificación de Prompts
- JSON Schema Reference - Referencia para escribir inputSchemas
- MCP Server Examples - Repositorio oficial de MCP servers de referencia
- OWASP Input Validation Cheat Sheet - Guía de seguridad para validación de inputs
Siguiente cápsula: Flujo completo request-response — traza un request desde el usuario hasta el Server y de vuelta, uniendo Host, Client, y Server en un diagrama completo. Incluye el mini-proyecto del módulo.