Este proyecto implementa un servidor completo del Model Context Protocol (MCP) para el dominio de crédito automotriz, utilizando Arquitectura Hexagonal (también conocida como Ports and Adapters). Esta combinación proporciona una base sólida, escalable y mantenible para sistemas de IA que necesitan integrarse con múltiples servicios externos y bases de datos.
La Arquitectura Hexagonal, creada por Alistair Cockburn, es un patrón arquitectónico que separa la lógica de negocio del mundo exterior mediante el uso de puertos y adaptadores.
- Dominio (Core): La lógica de negocio pura, sin dependencias externas
- Puertos: Interfaces que definen contratos de comunicación
- Adaptadores: Implementaciones concretas que conectan con tecnologías específicas
- Inversión de Dependencias: El dominio no conoce los detalles de implementación externa
- Aislamiento del Dominio: La lógica de negocio está protegida de cambios en APIs externas
- Testabilidad: Fácil creación de mocks y tests unitarios
- Flexibilidad: Cambiar proveedores de servicios sin afectar la lógica de negocio
- Escalabilidad: Agregar nuevos adaptadores sin modificar el core
- Mantenibilidad: Separación clara de responsabilidades
src/main/java/mx/regional/next/automotive/credit/
├── domain/ # 🎯 CAPA DE DOMINIO
│ ├── entities/ # Entidades de negocio
│ ├── valueobjects/ # Objetos de valor
│ ├── services/ # Servicios de dominio
│ └── ports/ # Puertos (interfaces)
│ ├── in/ # Puertos de entrada (use cases)
│ └── out/ # Puertos de salida (repositories, clients)
├── application/ # 🚀 CAPA DE APLICACIÓN
│ ├── usecases/ # Casos de uso (orquestación)
│ └── dtos/ # DTOs de transferencia
├── infrastructure/ # 🔧 CAPA DE INFRAESTRUCTURA
│ ├── mcp/ # Adaptadores MCP
│ │ ├── tools/ # Herramientas MCP
│ │ ├── resources/ # Recursos MCP
│ │ └── prompts/ # Prompts MCP
│ ├── persistence/ # Adaptadores de persistencia
│ │ ├── jpa/ # Implementaciones JPA
│ │ └── repositories/ # Repositorios concretos
│ ├── external/ # Adaptadores externos
│ │ ├── clients/ # Clientes Feign
│ │ ├── dtos/ # DTOs externos
│ │ ├── fallbacks/ # Circuit breakers
│ │ └── config/ # Configuraciones
│ └── config/ # Configuraciones generales
├── shared/ # 🛠️ UTILIDADES COMPARTIDAS
│ ├── constants/ # Constantes de negocio
│ └── utils/ # Utilidades
└── resources/ # 📁 RECURSOS ESTÁTICOS
├── mcp/ # Recursos MCP
│ ├── prompts/ # Templates de prompts
│ └── policies/ # Políticas de negocio
└── application.yml # Configuración Spring
Esta es el corazón del sistema. Contiene toda la lógica de negocio pura, sin dependencias externas.
Las entidades representan los conceptos fundamentales del negocio:
// CreditApplication: Representa una solicitud de crédito
public class CreditApplication {
private CreditApplicationId id;
private CustomerId customerId;
private VehicleInformation vehicle;
private CreditAmount amount;
private InterestRate rate;
private LoanTerm term;
private ApplicationStatus status;
// Lógica de negocio pura
public void approve() { /* validaciones y cambio de estado */ }
public void reject(String reason) { /* lógica de rechazo */ }
}¿Por qué son importantes?
- Encapsulan reglas de negocio críticas
- Son independientes de frameworks
- Garantizan consistencia de datos
- Facilitan testing unitario
Representan conceptos que se identifican por su valor, no por identidad:
// CreditAmount: Garantiza que los montos sean válidos
public record CreditAmount(BigDecimal value) {
public CreditAmount {
if (value == null || value.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Credit amount must be positive");
}
if (value.compareTo(MAX_CREDIT_AMOUNT) > 0) {
throw new IllegalArgumentException("Credit amount exceeds maximum");
}
}
}Beneficios:
- Inmutabilidad garantizada
- Validaciones en construcción
- Expresividad del código
- Prevención de errores de tipo
Contienen lógica de negocio que no pertenece a una entidad específica:
@Service
public class RiskCalculationService {
public RiskCategory calculateRisk(CreditApplication application,
CreditScore score,
EmploymentInfo employment) {
// Algoritmo complejo de cálculo de riesgo
// Combina múltiples factores
// Aplica reglas de negocio específicas
}
}Son interfaces que definen contratos de comunicación:
// Define QUÉ puede hacer el sistema
public interface ProcessCreditApplicationUseCase {
CreditApplicationResult process(ProcessCreditApplicationCommand command);
}// Define QUÉ necesita el sistema del exterior
public interface CreditApplicationRepository {
void save(CreditApplication application);
Optional<CreditApplication> findById(CreditApplicationId id);
}
public interface CreditBureauPort {
CreditScore getCreditScore(DocumentNumber document);
}Esta capa orquesta los casos de uso y coordina el flujo de trabajo.
Implementan los puertos de entrada y orquestan la lógica:
@UseCase
public class ProcessCreditApplicationUseCaseImpl implements ProcessCreditApplicationUseCase {
private final CreditApplicationRepository repository;
private final CreditBureauPort creditBureau;
private final RiskCalculationService riskService;
@Override
public CreditApplicationResult process(ProcessCreditApplicationCommand command) {
// 1. Validar datos de entrada
// 2. Consultar score crediticio
// 3. Calcular riesgo
// 4. Determinar aprobación
// 5. Guardar resultado
// 6. Notificar resultado
}
}Objetos para transferir datos entre capas:
public record ProcessCreditApplicationCommand(
String documentNumber,
String documentType,
BigDecimal requestedAmount,
Integer termMonths,
VehicleInformationDto vehicle,
PersonalInformationDto personal
) {}Contiene todas las implementaciones concretas y detalles técnicos.
Las herramientas MCP permiten que los LLMs ejecuten acciones específicas:
@Component
public class ProcessCreditApplicationTool {
@McpTool(
name = "process_credit_application",
description = "Procesa una nueva solicitud de crédito automotriz"
)
public CreditApplicationResult process(
@McpParameter(name = "document_number") String documentNumber,
@McpParameter(name = "requested_amount") BigDecimal amount
// ... más parámetros
) {
// Delega al caso de uso correspondiente
return processUseCase.process(new ProcessCreditApplicationCommand(...));
}
}¿Por qué esto es poderoso?
- Los LLMs pueden ejecutar lógica de negocio compleja
- Acceso controlado y validado a funcionalidades
- Trazabilidad completa de acciones
- Integración natural con conversaciones de IA
Los recursos MCP proporcionan datos estructurados a los LLMs:
@Component
public class VehicleCatalogResource {
@McpResource(
uri = "vehicle://catalog/brands",
name = "Catálogo de Marcas de Vehículos",
description = "Lista completa de marcas autorizadas para crédito"
)
public List<VehicleBrandDto> getAuthorizedBrands() {
return vehicleCatalogService.getAuthorizedBrands();
}
}Templates estructurados para guiar las conversaciones:
@Component
public class DocumentValidatorPrompt {
@McpPrompt(
name = "validate_customer_documents",
description = "Valida documentos de cliente para crédito automotriz"
)
public String createPrompt(
@McpParameter(name = "documents") List<DocumentDto> documents
) {
return promptTemplate.render("document_validation", Map.of(
"documents", documents,
"requirements", getDocumentRequirements()
));
}
}Implementan los puertos de salida para bases de datos:
@Repository
public class JpaCreditApplicationRepository implements CreditApplicationRepository {
private final CreditApplicationJpaRepository jpaRepository;
private final CreditApplicationMapper mapper;
@Override
public void save(CreditApplication application) {
CreditApplicationEntity entity = mapper.toEntity(application);
jpaRepository.save(entity);
}
}Integración con servicios externos:
@FeignClient(
name = "credit-bureau-client",
url = "${external.services.credit-bureau.url}",
fallback = CreditBureauClientFallback.class
)
public interface CreditBureauClient {
@PostMapping("/api/v1/credit-report")
CreditBureauResponse getCreditReport(@RequestBody CreditBureauRequest request);
}Manejo de fallos y degradación elegante:
@Component
public class CreditBureauClientFallback implements CreditBureauClient {
@Override
public CreditBureauResponse getCreditReport(CreditBureauRequest request) {
log.warn("Credit Bureau service unavailable, using fallback");
return CreditBureauResponse.builder()
.status("SERVICE_UNAVAILABLE")
.fallbackUsed(true)
.build();
}
}Centralizan todas las reglas de negocio:
public final class CreditConstants {
public static final class AmountLimits {
public static final BigDecimal MIN_CREDIT_AMOUNT = BigDecimal.valueOf(50_000);
public static final BigDecimal MAX_CREDIT_AMOUNT = BigDecimal.valueOf(2_000_000_000);
}
public static final class CreditScoreRanges {
public static final int EXCELLENT_THRESHOLD = 750;
public static final int GOOD_THRESHOLD = 550;
}
}Utilidades de validación reutilizables:
public final class ValidationUtils {
public static boolean isValidCreditAmount(BigDecimal amount) {
return amount != null
&& amount.compareTo(MIN_CREDIT_AMOUNT) >= 0
&& amount.compareTo(MAX_CREDIT_AMOUNT) <= 0;
}
public static boolean isValidDocumentNumber(String documentNumber) {
return documentNumber != null
&& DOCUMENT_PATTERN.matcher(documentNumber).matches();
}
}LLM Request → MCP Tool → Use Case → Domain Service → External API
↑ ↓
MCP Response ← DTO ← Domain Entity ← Repository ← Database
// Test unitario del dominio (sin dependencias externas)
@Test
void shouldCalculateRiskCorrectly() {
// Given: objetos de dominio puros
var application = new CreditApplication(...);
var score = new CreditScore(750);
// When: lógica de dominio pura
var risk = riskService.calculateRisk(application, score);
// Then: verificación directa
assertThat(risk).isEqualTo(RiskCategory.LOW);
}
// Test de integración MCP (con mocks)
@Test
void shouldProcessCreditApplicationViaMcp() {
// Given: mock de servicios externos
when(creditBureauPort.getCreditScore(any())).thenReturn(mockScore);
// When: llamada a través de MCP tool
var result = mcpTool.process("12345678", BigDecimal.valueOf(100_000));
// Then: verificación de resultado
assertThat(result.isApproved()).isTrue();
}// Cambiar de provider sin afectar la lógica de negocio
@Profile("datacredito")
@Component
public class DatacreditoAdapter implements CreditBureauPort { ... }
@Profile("experian")
@Component
public class ExperianAdapter implements CreditBureauPort { ... }- Dominio: Evoluciona según reglas de negocio
- MCP Tools: Evolucionan según necesidades de IA
- Integraciones: Evolucionan según APIs externas
- Persistencia: Evoluciona según requerimientos de datos
@McpTool(name = "diagnose_credit_application")
public DiagnosticResult diagnose(String applicationId) {
return DiagnosticResult.builder()
.domainState(getDomainState(applicationId))
.externalServices(checkExternalServices())
.mcpMetrics(getMcpMetrics())
.build();
}Cliente → Conversación con IA → MCP Tool → Validación de Documentos
↓
Consulta Score Crediticio → Valuación de Vehículo → Cálculo de Riesgo
↓
Decisión de Aprobación → Cálculo de Tasas → Generación de Contrato
Cliente: "¿Cuál es el estado de mi solicitud 12345?"
↓
MCP Tool: GetCreditApplicationStatusTool
↓
Use Case: GetApplicationStatusUseCase
↓
Response: Estado detallado con explicaciones
Cliente: "¿Qué vehículo puedo comprar con mi presupuesto?"
↓
MCP Resource: VehicleCatalogResource
↓
MCP Prompt: VehicleRecommendationPrompt
↓
Respuesta: Recomendaciones personalizadas
- Java 21+
- Spring Boot 3.5.3
- Maven 3.9+
- Base de datos (H2 para desarrollo, PostgreSQL para producción)
# application.yml
mcp:
server:
port: 8080
name: "Automotive Credit MCP Server"
version: "1.0.0"
external:
services:
credit-bureau:
url: "https://api.creditbureau.com"
timeout: 30000
vehicle-valuation:
url: "https://api.vehiclevaluation.com"
timeout: 25000
spring:
datasource:
url: jdbc:h2:mem:automotive_credit
driver-class-name: org.h2.Driver# Desarrollo
mvn spring-boot:run
# Producción
java -jar automotive-credit-mcp-server.jar- Código limpio y mantenible
- Tests rápidos y confiables
- Fácil agregar nuevas funcionalidades
- Debugging simplificado
- Tiempo de desarrollo reducido
- Menor riesgo de errores
- Facilidad para cambiar proveedores
- Escalabilidad natural
- Acceso estructurado a funcionalidades
- Consistencia en respuestas
- Capacidad de introspección
- Evolución controlada de capacidades
// Diferentes configuraciones por tenant
@Component
public class TenantAwareCreditRules implements CreditRulesPort {
public CreditRules getRules(TenantId tenantId) {
return switch (tenantId.value()) {
case "BANCO_A" -> bankASpecificRules();
case "BANCO_B" -> bankBSpecificRules();
default -> defaultRules();
};
}
}// Historial completo de cambios
@Entity
public class CreditApplicationEvent {
private EventId id;
private CreditApplicationId aggregateId;
private EventType type;
private String eventData;
private Instant timestamp;
private UserId causedBy;
}// Modelos de ML como servicios de dominio
@Component
public class MlRiskScoringService implements RiskScoringPort {
public RiskScore calculateRisk(CreditApplicationData data) {
// Llamada a modelo ML
var prediction = mlModelClient.predict(data);
return new RiskScore(prediction.score(), prediction.confidence());
}
}Este arquetipo está diseñado para ser un punto de partida robusto. Para contribuir:
- Fork el repositorio
- Crear una rama feature
- Seguir los principios de arquitectura hexagonal
- Agregar tests comprehensivos
- Documentar cambios
Este proyecto está bajo la licencia MIT. Ver LICENSE para más detalles.
La combinación de Arquitectura Hexagonal con Model Context Protocol proporciona una base excepcional para construir sistemas de IA robustos, mantenibles y escalables. Este arquetipo demuestra cómo separar claramente las responsabilidades mientras se mantiene la flexibilidad para evolucionar tanto la lógica de negocio como las integraciones con IA.
La arquitectura hexagonal garantiza que los cambios en las tecnologías externas (APIs, bases de datos, frameworks de IA) no afecten el corazón del sistema, mientras que MCP proporciona una interfaz estándar para que los LLMs interactúen de manera natural y poderosa con el dominio de negocio.