MCP en Producción: El Pattern de Composición que Nadie Te Explica
MCP en producción: el pattern Read-Reason-Act que separa los MCP servers que escalan de los que fallan. Código real con el SDK oficial de Anthropic.
La Mayoría Implementa MCP Como si Fuera un CRUD
Tienes un MCP server con cinco tools. Cada tool hace una query a tu base de datos. El agent las llama en secuencia.
Eso no es MCP bien usado. Es un ORM con pasos extra.
*El problema real no es el protocolo. Es que confundes exposición de datos con composición de contexto.*
MCP no es un mecanismo para que un model llame funciones. Es un contrato estandarizado que define cómo un model consume contexto dinámico en tiempo de ejecución. La diferencia parece sutil. En producción, es la diferencia entre un agent que razona y uno que adivina.
Este artículo cubre el pattern de composición que separa los MCP servers que aguantan carga real de los que mueren en la primera demo.
---
Por Qué el Modelo Mental Estándar Falla
La mayoría piensa en MCP así:
❌ El enfoque habitual
→ Tool = función que el model puede ejecutar
→ Resource = archivo que el model puede leer
→ Prompt = template que el model puede usar
→ Resultado: tres cajones separados sin relación entre sí
Ese modelo produce servers frágiles. El model no tiene forma de entender qué contexto necesita antes de actuar. Llama tools a ciegas. Sobreescribe contexto. Genera loops.
✅ El enfoque correcto
→ Resource = la fuente de verdad sobre el estado actual
→ Tool = la acción que modifica ese estado
→ Prompt = la instrucción que conecta estado con acción
→ Resultado: un ciclo de razonamiento coherente
La diferencia no es filosófica. Es arquitectónica. Y tiene consecuencias directas en el código.
---
Arquitectura de Composición: El Patrón Read-Reason-Act
El pattern más robusto en producción sigue tres fases explícitas:
Fase 1 — Read: El model consume Resources para entender el estado actual.
Fase 2 — Reason: El model usa ese contexto para decidir qué Tool invocar.
Fase 3 — Act: La Tool ejecuta y actualiza el estado. El ciclo se reinicia.
Esto no es teoria. Es la diferencia entre un agent que toma decisiones informadas y uno que actúa sobre suposiciones.
1. Estructura tu MCP Server con esta separación explícita
```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListToolsRequestSchema,
CallToolRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
const server = new Server(
{ name: 'production-mcp-server', version: '1.0.0' },
{ capabilities: { resources: {}, tools: {}, prompts: {} } }
);
// FASE 1: Resources exponen estado — nunca lógica de negocio
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'state://current-pipeline',
name: 'Pipeline State',
description: 'Estado actual del pipeline de procesamiento',
mimeType: 'application/json',
},
{
uri: 'state://error-log',
name: 'Error Log',
description: 'Últimos errores del sistema con contexto',
mimeType: 'application/json',
},
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'state://current-pipeline') {
const state = await getPipelineState(); // tu lógica de negocio
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(state, null, 2),
}],
};
}
throw new Error(`Resource no encontrado: ${uri}`);
});
```
2. Tools con validación de precondiciones
Este es el punto donde el 90% de los servers fallan. Una Tool que no valida el estado antes de actuar corrompe el ciclo Read-Reason-Act.
```typescript
// FASE 3: Tools modifican estado — siempre con validación
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'process-batch') {
// Validación de precondiciones ANTES de actuar
const currentState = await getPipelineState();
if (currentState.status === 'processing') {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
reason: 'Pipeline ya en ejecución',
currentState: currentState.status,
suggestion: 'Lee el resource state://current-pipeline antes de reintentar',
}),
}],
isError: false, // No es un error del sistema — es información para el agent
};
}
const result = await processBatch(args.batchId, args.options);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
processed: result.count,
nextState: result.newStatus,
}),
}],
};
}
throw new Error(`Tool desconocida: ${name}`);
});
const transport = new StdioServerTransport();
await server.connect(transport);
```
El detalle crítico: devuelves información estructurada como respuesta, no como error. El model necesita saber qué pasó para razonar bien en la siguiente iteración. Si lanzas una excepción, rompes el ciclo.
---
El Error de Diseño que Destroza el Razonamiento del Model
El error más común en MCP servers de producción:
❌ Tools que devuelven datos crudos sin contexto
```json
{ "rows": [{"id": 1, "status": "failed"}] }
```
✅ Tools que devuelven contexto accionable
```json
{
"result": [{"id": 1, "status": "failed"}],
"summary": "1 de 47 registros fallaron",
"affectedResources": ["state://error-log"],
"suggestedActions": ["read-error-log", "retry-failed-record"]
}
```
*La diferencia real no está en los datos. Está en cuánto contexto le das al model para decidir el siguiente paso.*
Un model que recibe datos crudos tiene que inferir qué hacer a continuación. Un model que recibe contexto accionable ejecuta el ciclo en la mitad de tokens. En un agent de producción con cientos de iteraciones, eso se multiplica.
---
Prompts como Puentes entre Estado y Acción
La feature más infrautilizada de MCP son los Prompts. No son templates estáticos.
Son instrucciones dinámicas que el server genera basándose en el estado actual.
```typescript
import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [{
name: 'diagnose-pipeline',
description: 'Diagnóstico contextual del estado del pipeline',
arguments: [{
name: 'severity',
description: 'Nivel de detalle: quick | deep',
required: false,
}],
}],
}));
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'diagnose-pipeline') {
const state = await getPipelineState();
const errors = await getRecentErrors();
const severity = args?.severity ?? 'quick';
// El prompt se construye con contexto real — no es estático
const contextBlock = severity === 'deep'
? `Errores recientes:\n${JSON.stringify(errors, null, 2)}`
: `Total errores: ${errors.length}`;
return {
description: 'Diagnóstico con contexto actual del sistema',
messages: [{
role: 'user',
content: {
type: 'text',
text: `Analiza el estado del pipeline:\n\nEstado: ${state.status}\nProcesados: ${state.processed}\n${contextBlock}\n\nIdentifica el problema principal y propón una acción concreta usando las tools disponibles.`,
},
}],
};
}
throw new Error(`Prompt no encontrado: ${name}`);
});
```
Esto es lo que hace que un agent sea útil en producción. El Prompt no le dice al model qué pensar. Le da el contexto exacto que necesita para razonar bien.
---
Checklist de un MCP Server Listo para Producción
→ Resources exponen estado, no lógica de negocio
→ Tools validan precondiciones antes de ejecutar
→ Respuestas de tools incluyen contexto accionable, no solo datos
→ Errores del sistema usan `isError: true` — información para el agent usa `isError: false`
→ Prompts se generan dinámicamente con estado real, no son templates hardcodeados
→ Cada tool indica qué resources se ven afectados tras su ejecución
→ El server tiene logging estructurado — sin logs, no depuras en producción
---
Lo Que Realmente Distingue un MCP Server de Producción
Los MCP servers que sobreviven en producción no son los más complejos.
Son los que mantienen el ciclo Read-Reason-Act coherente en cada interacción.
Eso significa:
Resources que siempre reflejan el estado real del sistema
Tools que comunican claramente qué cambió
Prompts que reducen la carga de razonamiento del model
MCP no es una forma elegante de hacer function calling. Es un contrato entre tu sistema y el model. Si el contrato es claro, el model razona bien. Si es ambiguo, el model adivina — y en producción, adivinar escala fatal.
Construye el contrato bien desde el principio. El refactor de un MCP server mal diseñado es más doloroso que el de cualquier API tradicional, porque el comportamiento incorrecto del model no siempre produce un error obvio. Produce outputs plausibles pero equivocados.
Y eso es lo más difícil de detectar en producción.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

