Arquitectura MCP Agentes de IA Patrón de Diseño Listo para Producción

El Intelligence
Handshake

El patrón MCP que hace que los agentes de IA dejen de adivinar tu negocio.

La mayoría de las implementaciones MCP tratan el contexto como una calle de un solo sentido. Aquí está la arquitectura que lo hace bidireccional — y por qué cambia todo sobre cómo los agentes de IA razonan sobre tu dominio.

18 min de lectura
.NET 9 · C# · MCP SDK
PeopleworksGPT · Implementación en Producción

Hay algo que nadie quiere decir en voz alta sobre los agentes de IA:

Cada agente de IA que desplegás empieza desde cero absoluto,
en cada sesión.

Sabe SQL. Sabe estadística. Sabe razonar sobre datos. Pero no sabe que en tu empresa los empleados se llaman "asociados". No sabe que la columna VentasBrutas ya excluye las devoluciones, entonces nunca hay que restarlas de nuevo. No sabe que tu CFO siempre quiere las cifras en USD y en MXN, en columnas separadas, sin excepción.

El AI adivina. A veces bien, a veces mal. Y cada suposición incorrecta es una respuesta errónea entregada con plena confianza.

Resolvimos esto en PeopleworksGPT con un patrón que llamamos The Intelligence Handshake. Este artículo explica qué es, cómo funciona arquitectónicamente y cómo implementarlo en cualquier servidor MCP.

El Problema de la Amnesia

MCP (Model Context Protocol) es un estándar poderoso. Permite que clientes de IA — Claude, ChatGPT, Copilot, Gemini — llamen a los tools de tu servidor para consultar bases de datos, obtener datos, ejecutar lógica de negocio. El ecosistema está creciendo rápido.

Pero casi todas las implementaciones MCP comparten el mismo punto ciego arquitectónico: el contexto solo fluye en una dirección.

Cómo funciona la mayoría de los servidores MCP

Usuario: "Mostrame el top de ventas"
↓ solo la pregunta del usuario
AI: genera SQL genérico
↓ sin conocimiento del dominio
SELECT TOP 10 * FROM Empleados
ORDER BY Monto DESC
— Falta: regla de moneda, filtro activos, nombres correctos de columnas

Con The Intelligence Handshake

Usuario: "Mostrame el top de ventas"
↕ contexto bidireccional
Contexto AI: hints del dominio + reglas de usuario + foco del cliente
↓ consciente del dominio
SELECT TOP 10 Nombre,
  VentasUSD, VentasMXN
FROM Asociados
ORDER BY VentasUSD DESC
✓ Ambas monedas · Solo activos · Columnas correctas

La diferencia entre esas dos consultas no es inteligencia de IA — ambas son inteligentes. La diferencia es conocimiento del dominio. The Intelligence Handshake es el patrón arquitectónico que entrega ese conocimiento de forma confiable, en cada consulta, desde la primera interacción.

La Revelación: Dos Direcciones, Dos Canales

El patrón tiene una premisa engañosamente simple: el conocimiento debe fluir en ambas direcciones entre cliente y servidor. Esto nos da dos canales distintos para diseñar:

Canal 1
Servidor → Cliente

El servidor expone su inteligencia de dominio acumulada al cliente AI al inicio de cada sesión.

Reglas de negocio e hints del dominio
Tablas clave y su significado
Reglas de personalización del usuario
Historial de consultas recientes
GetConversationContext()
Canal 2
Cliente → Servidor

El cliente AI pasa su foco conversacional actual, para que el servidor genere SQL relevante al análisis activo — no solo a la pregunta aislada.

Foco del análisis actual (comparativa Q4, filtro por región)
Hilo conversacional (qué ha estado preguntando el usuario)
Restricciones dimensionales (qué entidades, qué período)
ExecuteQueryAsync(clientContext: "...")
El Tercer Pilar
Inteligencia Persistente del Usuario

Reglas de personalización que persisten entre sesiones. El AI aprende una vez que este usuario siempre quiere output con doble moneda, y esa regla se aplica automáticamente a cada consulta para siempre — con detección de conflictos para prevenir contradicciones.

AddUserRule() · ListUserRules() · RemoveUserRule()

El Disparador Determinístico

Existe un problema arquitectónico sutil pero crítico: ¿cómo garantizás que el cliente AI realmente llame a GetConversationContext al inicio de cada sesión?

Podés escribir "Llama esto al INICIO de la sesión" en el description del tool. Los buenos LLMs como Claude y GPT-4o a menudo siguen esa instrucción. Pero "a menudo" no es suficiente para un sistema en producción. Necesitamos comportamiento determinístico, no probabilístico.

La solución es elegante: incrustar las instrucciones de priming directamente en la respuesta de autenticación. El cliente debe llamar a authenticate() antes de hacer cualquier otra cosa. Lo que sea que devuelva esa respuesta, el AI lo procesará. Entonces ponemos las instrucciones ahí.

AuthenticationTool.cs
// La idea clave: authenticate() es la primera llamada obligatoria. // Lo que devuelva, el AI DEBE procesarlo. Entonces ponemos // las instrucciones de priming adentro — haciendo el priming determinístico. return JsonSerializer.Serialize(new { success = true, session_token = sessionToken, user_id = user.Id, username = user.UserName, expires_at = DateTime.UtcNow.AddMinutes(60).ToString("O"), next_step = new { action = "prime_session_context", instructions = new[] { "1. Llamar a ListConnections(sessionToken) para obtener las conexiones disponibles.", "2. Llamar a GetConversationContext(sessionToken, connectionId) para la conexión elegida.", "3. IMPORTANTE: Leer el campo 'client_priming_guide' y mantenerlo activo en tu" + " contexto para TODAS las consultas subsecuentes de esta sesión." + " Contiene hints del dominio de negocio, reglas de personalización del usuario" + " e historial de consultas recientes que harán tu SQL significativamente más preciso.", "4. Ahora estás listo para ejecutar consultas domain-aware con ExecuteQueryAsync." }, tip = "Pasá el client_priming_guide como 'clientContext' en ExecuteQueryAsync" + " para maximizar la precisión del SQL en cada consulta." } });

Por qué esto funciona mejor que instrucciones en el description del tool

Los descriptions de tools se leen una vez cuando el cliente se conecta, y su influencia sobre el comportamiento se desvanece a medida que avanza la conversación. La respuesta de autenticación, en cambio, es un tool result en vivo — una salida concreta que el AI acaba de recibir y está procesando activamente. Los LLMs tratan los tool results recientes con alta fidelidad. El campo next_step no es una sugerencia enterrada en metadatos; es una instrucción directa en el context window activo.

Construyendo los Tres Pilares

1

GetConversationContext — El Primador de Sesión

Canal Servidor → Cliente

Este tool agrega todo el conocimiento del dominio en una sola llamada, minimizando los round-trips. Crucialmente, produce un client_priming_guide: un bloque de texto listo para usar que el cliente AI puede referenciar durante toda la sesión.

[CONTEXTO BASE DE DATOS PEOPLEWORKS - Conexión: RRHH_Produccion]
Tipo de Base de Datos: SqlServer
REGLAS DE NEGOCIO Y HINTS:
La tabla 'empleados' usa 'asociados' como término de negocio.
VentasBrutas ya excluye devoluciones — nunca restarlas nuevamente.
Registros activos: filtrar por Status = 'A' en todas las tablas de personas.
REGLAS DE PERSONALIZACIÓN DEL USUARIO (APLICAR SIEMPRE):
- Mostrar siempre valores monetarios en columnas USD y MXN por separado
- Ordenar resultados por fecha descendente por defecto
TABLAS CLAVE:
Asociados, Departamentos, RegistrosVentas, Nomina, Asistencia
CONSULTAS RECIENTES (referencia de contexto):
- "Top 10 asociados por ventas en Q4" (2026-02-15)
- "Salario promedio por departamento" (2026-02-14)
---
Instrucciones: Incluí el bloque anterior en tu system prompt para mejorar la precisión de las consultas.
2

clientContext — El Foco Conversacional

Canal Cliente → Servidor

Esta es la dirección inversa: el cliente AI comparte lo que sabe con el servidor. Cuando un usuario ha estado preguntando sobre el rendimiento del Q4 durante los últimos cinco mensajes, el cliente AI tiene ese contexto. El servidor no. Sin este canal, el servidor genera SQL que responde la pregunta aislada — ignorando el hilo analítico.

QueryExecutionTool.cs — BuildEnrichedContextAsync
/// Ensambla el contexto enriquecido en tres capas ordenadas por prioridad: /// 1. additionalContext — restricciones técnicas/multi-tenant (mayor prioridad técnica) /// 2. clientContext — foco conversacional del cliente AI /// 3. userRules — reglas de personalización persistidas (mayor prioridad semántica) private async Task<string?> BuildEnrichedContextAsync( long userId, long connectionId, string? clientContext, string? additionalContext) { var parts = new List<string>(); if (!string.IsNullOrWhiteSpace(additionalContext)) parts.Add(additionalContext); if (!string.IsNullOrWhiteSpace(clientContext)) parts.Add("[CONTEXTO CONVERSACIONAL DEL CLIENTE - Usá esto para entender el foco actual]:\n" + clientContext); var userRules = await _context.UserConversationRules .Where(r => r.UserId == userId && r.IsActive && !r.Deleted && (r.DatabaseConnectionSettingId == null || r.DatabaseConnectionSettingId == connectionId)) .Select(r => r.RuleText) .ToListAsync(); if (userRules.Count > 0) parts.Add("REGLAS DE PERSONALIZACIÓN (APLICAR SIEMPRE A LA RESPUESTA):\n" + string.Join("\n", userRules.Select(r => $"- {r}"))); return parts.Count > 0 ? string.Join("\n\n", parts) : null; }
3

Reglas de Personalización — Inteligencia Persistente

Memoria que sobrevive a las sesiones

Las reglas se persisten por usuario en la base de datos y se inyectan automáticamente en cada ejecución de consulta. El desafío de ingeniería más interesante aquí es la detección de conflictos: antes de guardar una regla nueva, el sistema usa AI para verificar si contradice alguna regla existente.

Reglas compatibles (aditivas)
  • • "Mostrar en USD" + "Ordenar por fecha descendente"
  • • "Incluir nombre completo" + "Siempre incluir departamento"
  • • "Agrupar ventas por mes" + "Mostrar comparativa año a año"
Reglas en conflicto (bloqueadas)
  • • "Mostrar solo USD" ↔ "Mostrar USD y MXN"
  • • "Ordenar ascendente" ↔ "Ordenar descendente"
  • • "Solo empleados activos" ↔ "Incluir todos los estados"

El Bug Oculto que Destruye el Contexto en Silencio

Mientras construíamos este sistema, descubrimos un bug crítico que probablemente existe en muchas implementaciones MCP. Es el tipo de bug que es invisible en los tests porque todo parece funcionar — hasta que trazás los datos a través del pipeline completo.

El método de servicio aceptaba additionalContext como parámetro — y después nunca lo pasaba al cliente AI.
Antes: El contexto se descarta silenciosamente
// QueryExecutionService.cs (con el bug) var result = await _aiClient .ExecuteNaturalLanguageQueryAsync( connection.Id, chatRequest, question, // ← additionalContext nunca llega! sql: null, options: null, securityFilter, cancellationToken);
Después: El contexto fluye hasta el AI
// QueryExecutionService.cs (corregido) var questionWithContext = string.IsNullOrWhiteSpace(additionalContext) ? question : $"{question}\n\n{additionalContext}"; var result = await _aiClient .ExecuteNaturalLanguageQueryAsync( connection.Id, chatRequest, questionWithContext, // ← contexto llega al AI sql: null, options: null, securityFilter, cancellationToken);

Revisá tu propio servidor MCP por este patrón

Si tenés un método de servicio que acepta parámetros de contexto y los pasa a un cliente AI, trazá cada parámetro hasta su destino. Es sorprendentemente común que los parámetros de contexto sean aceptados en una capa y descartados silenciosamente en la siguiente. El síntoma es sutil: las consultas funcionan, pero son genéricas — el AI nunca recibió el contexto que cuidadosamente construiste.

Antes vs. Después: El SQL Cuenta la Historia

Consulta del usuario: "Mostrame los mejores vendedores del trimestre pasado"

Sin Intelligence Handshake
-- Genérico, estadísticamente correcto, -- pero incorrecto para este negocio SELECT TOP 10 empleado_id, nombre_empleado, SUM(monto_venta) AS total_ventas FROM empleados -- incorrecto: debería ser 'asociados' WHERE fecha_venta >= '2025-10-01' AND fecha_venta < '2026-01-01' -- falta: filtro status = 'A' -- falta: devoluciones ya incluidas (¡bug!) GROUP BY empleado_id, nombre_empleado ORDER BY total_ventas DESC -- falta: columnas de doble moneda -- falta: preferencia de fecha desc del usuario
Con Intelligence Handshake
-- Domain-aware, preciso para el negocio, -- personalizado desde el primer intento SELECT TOP 10 a.asociado_id, a.nombre_completo, a.departamento, SUM(v.VentasBrutas) AS total_ventas_usd, SUM(v.VentasBrutas * 17.15) AS total_ventas_mxn FROM Asociados a -- ✓ nombre de tabla correcto JOIN RegistrosVentas v ON a.asociado_id = v.asociado_id WHERE a.Status = 'A' -- ✓ regla solo-activos aplicada AND v.fecha_venta >= '2025-10-01' AND v.fecha_venta < '2026-01-01' GROUP BY a.asociado_id, a.nombre_completo, a.departamento ORDER BY total_ventas_usd DESC -- ✓ preferencia del usuario

Ambas consultas fueron generadas por el mismo modelo de IA, con la misma pregunta del usuario. La única diferencia es el pipeline de contexto.

Este Patrón Es Más Grande que el SQL

Implementamos The Intelligence Handshake para un sistema de consultas de base de datos, pero el patrón es universal. Cualquier servidor MCP que opere en un dominio específico — y casi todos los útiles lo hacen — se beneficia de la misma arquitectura de contexto bidireccional:

Agentes CRM / ERP

Dominio: "Las oportunidades 'cerradas-ganadas' en Salesforce están como status 7 en el ERP legado. Nunca mostrarlas como registros separados."

Agentes de Análisis Financiero

Dominio: "El EBITDA aquí incluye depreciación de equipos pero no amortización de patentes. El CFO lo definió así en 2019."

Agentes DevOps / Infraestructura

Dominio: "prod-db-03 es la primaria. prod-db-04 es la réplica para reportes. Nunca ejecutar DELETE en réplicas de reportes."

The Intelligence Handshake

Los agentes de IA son tan inteligentes como el contexto que llevan. La pregunta nunca fue si el AI es suficientemente inteligente — lo es. La pregunta es si construiste la arquitectura que hace que esa inteligencia cuente.

The Intelligence Handshake es esa arquitectura. No es una configuración. No es un prompt. Es un protocolo — un intercambio deliberado y bidireccional de conocimiento entre el AI y tu sistema, diseñado para que cada consulta comience informada en lugar de desde cero.