KeelFramework

Keel Framework / Keel/MCP / Java

Developer Scaffolding

Esta sección describe cómo Keel materializa su arquitectura de referencia en un MCP Server generado a través del scaffolding, proporcionando una estructura inicial estandarizada sobre la cual los equipos pueden desarrollar sus capacidades funcionales.

El scaffolding combina la estructura arquitectónica, los módulos técnicos reutilizables y las convenciones de desarrollo definidas por Keel, proporcionando una base común para la construcción de nuevos MCP Servers.

Scaffolding Arquitectónico

Desde Keel-Framework/MCP proponemos un scaffolding base para MCP Servers que integra los starters y módulos fundamentales que componen la arquitectura Keel.

Este scaffolding proporciona una estructura inicial estandarizada y preconfigurada, permitiendo a los equipos de desarrollo centrarse desde el inicio en la implementación de la lógica de dominio, garantizando al mismo tiempo la integración con las capacidades técnicas y transversales proporcionadas por Keel.

El scaffolding genera una estructura estandarizada de módulos y paquetes, organizada por responsabilidad, proporcionando la base arquitectónica sobre la cual se implementará el MCP Server.

Cada módulo encapsula un ámbito técnico o funcional específico, favoreciendo la separación de responsabilidades, el bajo acoplamiento y la evolución independiente de los componentes.

Módulo / Carpeta Tipo Responsabilidad Contenido
_boot Módulo Maven Arranque y configuración del servidor Módulo ejecutable que contiene Application.java, bootstrap.yml, application*.yml y la configuración necesaria para iniciar la aplicación. Puede empaquetarse como WAR desplegable a través de ServletInitializer o ejecutarse localmente como aplicación Spring Boot.
_mcp Módulo Maven Implementación de las capacidades MCP Contiene los componentes relacionados con el MCP Server, incluyendo Tools, Resources y Prompts, así como la lógica funcional expuesta a través del protocolo MCP y los componentes necesarios para integrarse con el framework.
_model Módulo Maven Modelo de dominio Contiene modelos, DTOs, entidades y mappers basados en MapStruct, así como las estructuras de datos y contratos utilizados por los diferentes módulos del proyecto.

Ejemplo de un proyecto generado por el scaffolding:

El siguiente ejemplo muestra la estructura de un MCP Server generado con el Keel Maven Archetype.

Modules

Nomenclatura de Módulos

Los módulos estándar definidos en el Keel Maven Archetype utilizan identificadores base (_boot, _mcp y _model) que se renombran automáticamente durante la generación del proyecto, incorporando el nombre del proyecto manteniendo el literal estándar definido por la arquitectura.

Por ejemplo, para un proyecto denominado:

  • Nombre del proyecto: sales-mcp-framework

Los módulos generados serán:

  • sales-mcp-boot
  • sales-mcp-mcp
  • sales-mcp-model

Esto mantiene una nomenclatura homogénea y estandarizada para todos los proyectos generados desde el Keel Maven Archetype.

Estructura Física del Proyecto

El Keel Maven Archetype genera una estructura base de MCP Server lista para comenzar a desarrollar las capacidades funcionales del proyecto.

La estructura generada se organiza mediante una clara separación de responsabilidades, distinguiendo los componentes relacionados con el arranque de la aplicación, la implementación de las capacidades MCP, el modelo de dominio y la configuración del proceso CI/CD.

Esta organización proporciona una base común para los MCP Servers generados con Keel, facilitando la estandarización de la estructura del proyecto, el bajo acoplamiento entre módulos y la evolución independiente de sus componentes.

Sobre esta estructura base, el equipo de desarrollo puede añadir la lógica específica del dominio y extender las capacidades MCP a través de Tools, Resources y Prompts, manteniendo las responsabilidades funcionales separadas de las capacidades técnicas proporcionadas por Keel.

La siguiente estructura muestra los módulos, paquetes y ficheros principales generados por el Archetype:

Scaffolding base de la estructura de paquetes:

<name-project-mcp>/
├── Dockerfile
├── pom.xml
│
├── _boot/
│   ├── pom.xml
│   └── src/
│       └── main/
│           ├── java/
│           │   └── Application.java
│           │
│           └── resources/
│               ├── application.yml
│               ├── application-dev.yml
│               ├── application-pre.yml
│               ├── application-pro.yml
│               ├── bootstrap.yml
│               └──user-truststore.jks
│
├── _mcp/
│   ├── pom.xml
│   ├── prompts/
│   │   └── Prompts.java
│   ├── resources/
│   │   └── Resources.java
│   └── tools/
│       └── Tools.java
│
├── _model/
│   ├── pom.xml
│   ├── dto/
│   │   └── HelloWorldItemDTO.java
│   ├── entity/
│   │   └── HelloWorldItemEntity.java
│   ├── errors/
│   │   └── ExemploErrorsMsg.java
│   └── mapper/
│       └── HelloWorldItemMapper.java
│
└── pom.json

Configuración del MCP Server

La configuración del MCP Server generado por Keel se centraliza principalmente en el fichero application.yml, ubicado en el módulo _boot bajo src/main/resources.

Este fichero contiene la configuración principal de la aplicación y permite definir los parámetros necesarios para que el servidor arranque y funcione en los diferentes entornos de ejecución.

Esta sección se divide en 8 aspectos de configuración técnica que describen el propósito de cada bloque del MCP Server generado por Keel, proporcionando el contexto necesario para entender qué controla cada propiedad antes de modificarla.

(1-8) Identidad de la Aplicación

El bloque de configuración spring define la identidad y el comportamiento base de la aplicación Spring Boot.

spring:
  application:
    name: keel-mcp-sample
  threads:
    virtual:
      enabled: true
  main:
    web-application-type: servlet
Property Descripción
spring.application.name Nombre lógico de la aplicación, utilizado en logs, métricas y trazas.
spring.threads.virtual.enabled Habilita Virtual Threads (Java 21+). Cada petición y cada invocación de Tool se ejecuta en un hilo virtual ligero, lo que mejora la escalabilidad en cargas intensivas de I/O (llamadas REST a backends) sin necesidad de grandes pools de hilos de plataforma.
spring.main.web-application-type servlet fuerza el stack Servlet (Tomcat / WebMVC), coherente con el transporte síncrono Streamable HTTP.

(2-8) Protocol Transport

El bloque spring.ai.mcp.server configura el comportamiento del servidor MCP y define el tipo de servidor, el protocolo de transporte y los mecanismos utilizados para registrar las capacidades MCP.


spring:
  ai:
    mcp:
      server:
        type: SYNC
        protocol: STREAMABLE
        stdio:
          enabled: false
        streamable-http:
          mcp-endpoint: /mcp
          keep-alive-interval: 30s
        annotation-scanner:
          enabled: true
Property Descripción
type: SYNC El servidor MCP opera en modo síncrono (McpSyncServer), no reactivo.
protocol: STREAMABLE Utiliza el transporte Streamable HTTP, el recomendado por la especificación MCP para servidores remotos.
stdio.enabled: false Desactiva el transporte por consola: este servidor se ejecuta como un servicio HTTP independiente, no como subproceso local de un cliente.
streamable-http.mcp-endpoint Ruta en la que se expone el protocolo MCP.
streamable-http.keep-alive-interval Se envía un ping cada 30 s para mantener viva la conexión con el cliente.
annotation-scanner.enabled Habilita el escaneo automático de beans anotados con @McpTool, @McpResource, @McpPrompt y @McpComplete.

(3-8) Comunicación TLS

El bloque server define el puerto HTTP utilizado por la aplicación y la configuración TLS del servidor.

server:
  port: 8080
  ssl:
    enabled: false
Property Descripción
server.port: 8080 Puerto HTTP en el que escucha la aplicación.
server.ssl.enabled: false Desactiva TLS directamente en Tomcat. En el entorno de despliegue, TLS puede terminarse en una capa anterior como un Ingress, Load Balancer o API Gateway.

(4-8) Adapters

La configuración keel.adapters proporciona capacidades reutilizables para la comunicación externa y la gestión de caché de autenticación. El adapter rest-client organiza la configuración de SSL, servicios backend y cliente HTTP, mientras que el adapter cache proporciona la gestión de caché.


keel:
  adapters:
    rest-client:
      ssl:
        enabled: true
        trust-store: ${TRUSTSTORE_LOCAL}
        trust-store-password: ${TRUSTSTORE_PASSWORD}
        trust-store-type: ${TRUSTSTORE_TYPE}

      services: {}
      # Ejemplo de servicio backend:
      # services:
      #   name-service:
      #     base-url: https://api.example.sca.es
      #     log-requests: true

      http-client:
        socket-timeout: 60000
        connect-timeout: 60000
        request-timeout: 60000
        max-total-connections: 200
        max-connections-per-route: 20
        connection-time-to-live: 300000
        keep-alive: 10000

    cache:
      expire-after-write: 30m
      expire-after-access: 30m
      maximum-size: 500
      record-stats: true

keel.adapters.rest-client.ssl:
Configuración TLS compartida por todas las llamadas salientes; services.<name> registra cada backend que los Tools pueden invocar a través de McpRestClientFactory. Cada servicio se identifica por su clave (product) y se resuelve por nombre desde el código.

Property Descripción
ssl.enabled: true Habilita SSL/TLS para las llamadas salientes realizadas por el adapter REST client, de modo que utiliza el trust store personalizado configurado a continuación.
ssl.trust-store Ruta al trust store que contiene los certificados (por ejemplo, la CA o el certificado del servidor destino) en los que confía el cliente al abrir conexiones HTTPS. Se obtiene de la variable de entorno TRUSTSTORE_LOCAL.
ssl.trust-store-password Contraseña utilizada para abrir el trust store. Se obtiene de la variable de entorno TRUSTSTORE_PASSWORD, para no almacenarla en el fichero de configuración.
ssl.trust-store-type Formato del fichero trust store, como JKS o PKCS12. Se obtiene de la variable de entorno TRUSTSTORE_TYPE.

keel.adapters.rest-client.services:
Registro de servicios backend que los Tools pueden invocar a través de McpRestClientFactory. Cada entrada se identifica por su clave (ej. name-service) y se resuelve por nombre desde el código. Por defecto el mapa está vacío ({}); descomente y añada las entradas según sea necesario.

Property Descripción
services: {} Mapa vacío por defecto. Cada clave registra un servicio backend nombrado disponible para los MCP tools en tiempo de ejecución.
services.<name>.base-url URL raíz del servicio backend (ej. https://keel.api.example.es). Todas las peticiones realizadas a través de este servicio se resuelven relativas a esta URL.
services.<name>.log-requests Cuando se establece a true, registra en log cada petición y respuesta HTTP de este servicio. Útil para depuración; debe desactivarse en producción para evitar filtrar datos sensibles.

keel.adapters.rest-client.http-client:
Configuración global del cliente HTTP aplicada a todas las llamadas salientes realizadas a través de McpRestClientFactory. Estos valores por defecto gobiernan timeouts, pool de conexiones y comportamiento keep-alive para todos los servicios registrados, salvo que un servicio específico los sobrescriba.

Property Descripción
socket-timeout: 60000 Tiempo máximo (en milisegundos) de espera para recibir datos en una conexión ya establecida. Si no llegan bytes en 60 s, la petición se aborta.
connect-timeout: 60000 Tiempo máximo (en milisegundos) para establecer una conexión TCP con el host remoto. El intento falla tras 60 s.
request-timeout: 60000 Tiempo máximo (en milisegundos) para obtener una conexión del pool. Evita que las peticiones se encolen indefinidamente cuando el pool está agotado.
max-total-connections: 200 Límite superior de conexiones simultáneas que puede mantener el pool entre todos los servicios backend.
max-connections-per-route: 20 Número máximo de conexiones simultáneas permitidas hacia un único host/ruta. Evita que un backend muy ocupado consuma todo el pool.
connection-time-to-live: 300000 Vida máxima (en milisegundos) de una conexión en el pool, independientemente de la actividad. Tras 5 minutos la conexión se descarta y se crea una nueva, ayudando a evitar conexiones obsoletas detrás de balanceadores de carga.
keep-alive: 10000 Duración keep-alive por defecto (en milisegundos) utilizada cuando la respuesta del servidor no incluye una cabecera Keep-Alive. Las conexiones inactivas se mantienen abiertas durante 10 s antes de ser candidatas a desalojo.

keel.adapters.cache:
Configuración de la caché en memoria Caffeine utilizada por el framework Keel. Estos ajustes controlan las políticas de expiración, la capacidad máxima y el registro de estadísticas para todas las entradas cacheadas gestionadas por el adapter.

Property Descripción
expire-after-write: 30m Tiempo tras el cual una entrada cacheada expira, medido desde el momento en que se escribió (o se reemplazó por última vez). Tras 30 minutos la entrada se desaloja independientemente de cuántas veces se lea.
expire-after-access: 30m Tiempo tras el cual una entrada cacheada expira, medido desde su último acceso de lectura o escritura. La ventana de 30 minutos se reinicia cada vez que se accede a la entrada, manteniendo vivos los datos de uso frecuente.
maximum-size: 500 Número máximo de entradas que puede contener la caché. Cuando se alcanza este límite, las entradas menos utilizadas recientemente se desalojan para hacer sitio a las nuevas.
record-stats: true Habilita la recopilación de estadísticas de aciertos/fallos/desalojos. Útil para monitorizar la efectividad de la caché a través de endpoints de métricas; puede desactivarse en producción si la sobrecarga no es deseada.

(5-8) Autenticación JWT del Transport

El bloque keel.mcp.transport.auth configura la autenticación de las peticiones dirigidas al MCP Server.

keel:
  mcp:
    transport:
      auth:
        enabled: true
        jwt:
          issuer: ${JWT_ISSUER:https://sso.example.com/realms/mcp}
          audience: ${JWT_AUDIENCE:mcp-server}
          jwks-ttl: 1h
        excluded-paths:
          - /actuator/health
          - /actuator/info
Property Descripción
auth.enabled El endpoint MCP requiere un JWT válido en la cabecera Authorization: Bearer.
jwt.issuer Proveedor de identidad OIDC (Keycloak / Red Hat SSO) que emite los tokens. Las claves públicas se obtienen del endpoint JWKS del issuer.
jwt.audience Audiencia esperada en el claim aud. Debe definirse: una audiencia no validada permite reutilizar tokens emitidos para otras aplicaciones.
jwt.jwks-ttl Tiempo durante el cual se cachean las claves públicas antes de refrescarse.
excluded-paths Rutas exentas de autenticación, necesarias para que las probes de Kubernetes / OpenShift funcionen sin token.

La validación de la audiencia debe configurarse según las políticas de seguridad del entorno. Una validación incompleta de los claims del JWT puede reducir el nivel de seguridad de la autenticación.

(6-8) Gestión de Sesiones del Transport

keel.mcp.transport.session:
Controla el ciclo de vida de las sesiones del transporte Streamable HTTP. Cada cliente que se conecta a través del endpoint /mcp crea una sesión independiente; estas propiedades limitan cuántas pueden estar activas simultáneamente y durante cuánto tiempo se mantienen vivas antes de ser descartadas.


keel:
  mcp:
    transport:
      session:
        max-sessions: 500
        session-timeout: 30m
        log-events: false
Property Descripción
max-sessions: 500 Número máximo de sesiones MCP concurrentes permitidas en el transporte Streamable HTTP. Una vez alcanzado este límite, los nuevos intentos de conexión se rechazan hasta que una sesión existente expire o se cierre.
session-timeout: 30m Tiempo de inactividad tras el cual una sesión ociosa se cierra automáticamente y sus recursos se liberan. La ventana de 30 minutos se reinicia con cada interacción del cliente.
log-events: false Cuando se establece a true, registra en log los eventos del ciclo de vida de las sesiones (creación, expiración, cierre). Desactivado por defecto para reducir el ruido en los logs de producción.

(7-8) Observabilidad

El bloque observability controla la generación de logs estructurados asociados a los diferentes tipos de eventos del MCP Server. Logs JSON estructurados, habilitados por nivel a través de variables de entorno:

keel:
  mcp:
    observability:
      enabled: ${MCP_OBSERVABILITY_ENABLED:true}
      technical: ${MCP_OBSERVABILITY_TECHNICAL:true}
      functional: ${MCP_OBSERVABILITY_FUNCTIONAL:true}
      security: ${MCP_OBSERVABILITY_SECURITY:true}
Nivel Qué registra
technical Peticiones y respuestas HTTP hacia los backends.
functional Invocaciones de MCP Tools: nombre, argumentos, resultado y duración.
security Eventos de autenticación JWT y gestión de sesiones.

(8-8) Actuator y logging

El bloque management configura los endpoints de Spring Boot Actuator, mientras que el bloque logging establece los niveles de logging de la aplicación.

management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics
  endpoint:
    health:
      show-details: when-authorized

logging:
  level:
    root: INFO
    io.github.keelframework.mcp: INFO
Property Descripción
management.endpoints.web.exposure.include Define los endpoints de Actuator expuestos por HTTP. En este caso: health, info y metrics.
management.endpoint.health.show-details Configura el nivel de detalle mostrado por el endpoint de health check.
logging.level.root Define el nivel de logging global de la aplicación.
logging.level.com.framework.observability.logging Define el nivel de logging específico para los componentes de observabilidad de Keel.

Expone /actuator/health, /actuator/info y /actuator/metrics. show-details: when-authorized muestra el detalle de los health indicators solo a usuarios autenticados; utilice always únicamente en entornos locales, ya que el detalle puede revelar hosts y estados de dependencias internas.

Desarrollo de MCP Tools

Esta sección describe el modelo de implementación de los MCP Tools en Keel, tomando el arquetipo Keel como arquitectura de referencia base para la generación de proyectos MCP Server basados en Spring AI.

Los Tools se desarrollan en el módulo <project>-mcp del proyecto generado, dentro del paquete <base-package>.mcp.tools (por ejemplo, io.github.keelframework.mcp.sample.tools).

@Tool — Implementación

Un Tool representa una capacidad de negocio expuesta por el MCP Server para que pueda ser invocada por un agente o un LLM. Aunque su implementación es relativamente sencilla, todos los Tools deben seguir un conjunto de convenciones que garantizan una arquitectura homogénea, mantenible y consistente entre proyectos.

Keel proporciona una estructura base que estandariza la implementación de Tools, incluyendo la organización del código, la comunicación con sistemas backend, el manejo de errores, la observabilidad y el registro de las capacidades disponibles para el modelo.

La implementación de un Tool involucra 7 aspectos técnicos, descritos en las siguientes secciones:

# Aspecto Elemento clave Obligatorio
1 Definición del método Paquete tools, Spring bean (@Component), firma tipada ✅
2 Anotación @Tool en el método description con el formato: QUÉ HACE / CUÁNDO USARLO / QUÉ DEVUELVE ✅
3 Anotación @ToolParam en los parámetros description (tipo + valores válidos + ejemplo), required ✅
4 Manejo de errores try/catch; lanzar McpToolException, que Keel convierte en un error MCP estructurado ✅
5 Observabilidad Traza con mcplog.logTool(service, path, httpStatus, duration) en éxito y en error ✅
6 Registro del Tool Bean explícito ToolCallbackProvider: MethodToolCallbackProvider.builder().toolObjects(bean).build() ✅
7 Convención de nombres Verbo + sustantivo, camelCase (ej. getProduct) ✅

Estructura estándar de un Tool (stack Spring AI)

Cada Tool desarrollado sobre Keel debe seguir una estructura común que facilite su comprensión tanto para los desarrolladores como para el LLM. Esta estructura incluye la definición del método, la descripción funcional a través de las anotaciones @Tool y @ToolParam, la invocación de servicios backend, el registro de trazas de observabilidad y el manejo uniforme de errores.

El siguiente ejemplo muestra la estructura recomendada:


package io.keelframework.sample.mcp.tools;
import io.keelframework.mcp.adapters.rest.client.McpRestClient;
import io.keelframework.mcp.adapters.rest.client.McpRestClientFactory;
import io.keelframework.mcp.adapters.rest.exception.RestClientException;
import io.keelframework.mcp.adapters.rest.model.RestResponse;
import io.keelframework.mcp.common.exceptions.McpToolException;
import io.keelframework.sample.model.dto.PetstoreDTO;
import io.keelframework.sample.model.errors.PetstoreErrorsMsg;
import jakarta.annotation.PostConstruct;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import io.keelframework.mcp.observability.logging.handler.McpAuthLoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;

@Component
public class PetstoreTools {

    private static final Logger log = LoggerFactory.getLogger(PetstoreTools.class);

    private static final String SERVICE_NAME = "petstore";
    private static final List<String> VALID_STATUSES = List.of("available", "pending", "sold");

    private McpRestClient petClient;
    private final McpRestClientFactory restClientFactory;
    private final McpAuthLoggingHandler mcplog;

    public PetstoreTools(McpRestClientFactory restClientFactory, McpAuthLoggingHandler mcplog) {
        this.restClientFactory = restClientFactory;
        this.mcplog = mcplog;
    }

    @PostConstruct
    void init(){
        this.petClient = restClientFactory.getClient(SERVICE_NAME);
    }

    @Tool(description = """
        Find a pet by its identifier.
        Use when the user asks about a specific pet or mentions an ID.
        Returns the pet's name, category and status.
        """)
    public PetstoreDTO.PetResponse findPet(
            @ToolParam(description = "Numeric ID of the pet. Example: 1, 2, 3")
            long petId) {

        long start = System.nanoTime();
        String path = "/pet/" + petId;
        try {
            RestResponse<PetstoreDTO.PetResponse> response = petClient.get(
                    "/pet/{petId}", PetstoreDTO.PetResponse.class, petId);

            long durationMs = (System.nanoTime() - start) / 1_000_000;
            mcplog.logTool(SERVICE_NAME, path, response.httpStatus(), durationMs);
            return response.body();
        }
        catch (RestClientException e) {
            long durationMs = (System.nanoTime() - start) / 1_000_000;
            mcplog.logTool(SERVICE_NAME, path, 500, durationMs);
            log.warn(PetstoreErrorsMsg.PET_NOT_FOUND_MSG, petId, path, e.getMessage());
            throw new McpToolException(SERVICE_NAME, path, 500,
                    "Could not retrieve pet " + petId, e);
        }
    }

    @Tool(description = """
        List pets filtered by status.
        Use when the user wants to see available, pending or sold pets.
        Returns the list with name, category, status and the total number of results.
        """)
    public PetstoreDTO.PetListResponse listPets(
            @ToolParam(description = "Status to filter by. Valid values: available, pending, sold.")
            String status) {

        if (!VALID_STATUSES.contains(status)) {
            log.warn(PetstoreErrorsMsg.PET_INVALID_STATUS_MSG, status);
            throw new McpToolException(SERVICE_NAME, "/pet/findByStatus", 400,
                    "Invalid status '" + status + "'. Valid values: available, pending, sold.");
        }

        long start = System.nanoTime();
        String path = "/pet/findByStatus";
        try {
            RestResponse<List<PetstoreDTO.PetResponse>> response =  petClient.getListWithQueryParams(
                    path, Map.of("status", status), PetstoreDTO.PetResponse[].class);

            long durationMs = (System.nanoTime() - start) / 1_000_000;
            mcplog.logTool(SERVICE_NAME, path, response.httpStatus(), durationMs);

            List<PetstoreDTO.PetResponse> pets = response.body();
            return new PetstoreDTO.PetListResponse(pets, pets.size());
        }
        catch (RestClientException e) {
            long durationMs = (System.nanoTime() - start) / 1_000_000;
            mcplog.logTool(SERVICE_NAME, path, 500, durationMs);
            log.warn(PetstoreErrorsMsg.PET_LIST_FAILED_MSG, status, path, e.getMessage());
            throw new McpToolException(SERVICE_NAME, path, 500,
                    "Could not retrieve the pet list for status '" + status + "'", e);
        }
    }

Obtención del RestClient en el Tool

Una vez que el backend está registrado en application.yml (véase REST adapter: backend services), el módulo adapter-rest-client crea automáticamente un McpRestClient asociado a ese <service-name>.

Keel no inyecta el cliente directamente: inyecta una factoría, McpRestClientFactory, a través del constructor, que proporciona el cliente para cada backend a partir del mismo nombre lógico utilizado en la configuración. Esto permite que el cliente se resuelva en tiempo de ejecución, y que el mismo Tool pueda comunicarse con varios backends sin cambiar su constructor.

private final McpRestClientFactory restClientFactory;

    private static final String SERVICE_NAME = "petstore";
    private McpRestClient petClient;

    public PetstoreTools(McpRestClientFactory restClientFactory, McpAuthLoggingHandler mcplog) {
        this.restClientFactory = restClientFactory;
    }

    @PostConstruct
    void init(){
        this.petClient = restClientFactory.getClient(SERVICE_NAME);
    }

Si el Tool utiliza un único backend, resuélvalo una vez en @PostConstruct como en el ejemplo; si necesita varios, llame a getClient(...) en cada método.

Paso a paso: componentes a inyectar:

Componente Rol
McpRestClientFactory Factoría que proporciona un McpRestClient ya configurado (base-url, TLS, timeouts) para un servicio dado, a través de getClient("<service-name>").
McpRestClient Cliente HTTP asociado a un backend específico. Expone get, post, put, delete, etc., y devuelve un model.io.keelframework.mcp.adapters.rest.RestResponse<T> con el body tipado y el código de estado HTTP.

1. Obtener el cliente del backend

Dentro del Tool (o una vez en @PostConstruct), resuelva el cliente con el nombre del servicio registrado en application.yml:


this.petClient = restClientFactory.getClient(SERVICE_NAME)

La cadena "product" debe coincidir exactamente con la clave definida en keel.mcp.adapters.rest.services.<service-name>. Si no coincide, la factoría lanza un IllegalArgumentException en el arranque.

2. Invocar el endpoint

Utilice client.get(...) (o el verbo HTTP correspondiente) con la ruta relativa, la clase de respuesta esperada y los parámetros de path o query. La base-url, TLS y timeouts ya están resueltos desde la configuración del adapter:


RestResponse<PetstoreDTO.PetResponse> response = petClient.get("/pet/{petId}", PetstoreDTO.PetResponse.class, petId);
return response.body();

Desarrollo de MCP Prompts

Esta sección describe el modelo de implementación de los MCP Prompts en Keel, tomando el arquetipo Keel como arquitectura de referencia base para la generación de proyectos MCP Server basados en Spring AI.

Los Prompts se desarrollan en el módulo <project>-mcp del proyecto generado, dentro del paquete <base-package>.mcp.prompt (por ejemplo, io.github.keelframework.mcp.sample.prompts).

@Prompt — Implementación

Un Prompt representa una plantilla de conversación expuesta por el MCP Server para guiar al LLM a través de flujos funcionales específicos del dominio de negocio cubierto por el servidor.

A diferencia de un Tool, que expone una capacidad de negocio ejecutable, un Prompt no ejecuta lógica por sí mismo: estructura y condiciona el razonamiento del LLM, indicándole qué Tools invocar, en qué orden, con qué validaciones y en qué formato de respuesta.

Aunque su implementación es relativamente sencilla, todos los Prompts deben seguir un conjunto de convenciones que garantizan una arquitectura homogénea, mantenible y consistente entre proyectos.

Keel proporciona una estructura base que estandariza la implementación de Prompts, incluyendo la organización del código, la definición de argumentos de entrada, la construcción de los mensajes de la plantilla y el control de flujos multi-tool.

Aspectos técnicos de la implementación de un Prompt

La implementación de un Prompt involucra seis aspectos técnicos:

# Aspecto Elemento clave Obligatorio
1 Definición del método Paquete prompts, Spring bean (@Component), método que retorna McpSchema.GetPromptResult ✅
2 Anotación @McpPrompt en el método name (formato: <context>-<action>, kebab-case) + description con el formato: QUÉ FLUJO GUÍA / CUÁNDO USARLO ✅
3 Anotación @McpArg en los parámetros description (tipo + formato/valores válidos + ejemplo), required ✅
4 Construcción de mensajes List<McpSchema.PromptMessage> con instrucciones explícitas: Tools a invocar, orden, dependencias entre pasos, formato de salida y manejo de resultados vacíos ✅
5 Rol del mensaje USER (el LLM decide libremente) vs USER + ASSISTANT (formato de respuesta forzado / few-shot) ✅
6 Convención de nombres del método Verbo + sustantivo, camelCase (ej. findProduct) ✅

Los Prompts anotados con @McpPrompt se registran automáticamente en el servidor gracias a spring.ai.mcp.server.annotation-scanner.enabled: true; a diferencia de los Tools, no requieren un bean de registro explícito.

Estructura estándar de un Prompt

Cada Prompt desarrollado sobre Keel debe seguir una estructura común que facilite su comprensión tanto para los desarrolladores como para el LLM. Esta estructura incluye la definición del método, la descripción funcional a través de las anotaciones @McpPrompt y @McpArg, la construcción de la plantilla de mensajes (McpSchema.GetPromptResult) que guía al LLM en la invocación de los Tools necesarios, y la definición explícita del formato de respuesta esperado.

El siguiente ejemplo muestra la estructura recomendada:


package io.keelframework.sample.mcp.prompts;
@Component
public class PetstorePrompts {

    @McpPrompt(
            name = "petstore-find-pet",
            description = "Template for looking up a pet by its ID. " +
                    "Use when the user wants full details about one specific pet."
    )
    public McpSchema.GetPromptResult findPetPrompt(
            @McpArg(description = "Numeric ID of the pet. Example: 1, 2, 3")
            String petId) {
        return new McpSchema.GetPromptResult(
                "Look up a pet by ID",
                List.of(
                        new McpSchema.PromptMessage(
                                McpSchema.Role.USER,
                                new McpSchema.TextContent(
                                        "Give me the full details of the pet with ID " + petId + ".\n" +
                                                "Include its name, category and current status."))
                ));
    }

    @McpPrompt(
            name = "petstore-register-pet",
            description = "Template for registering a new pet, guiding the model to " +
                    "collect name, category and status before calling the registerPet tool. " +
                    "Use when the user wants to add a pet but hasn't given all the required fields yet."
    )
    public McpSchema.GetPromptResult registerPetPrompt() {

        return new McpSchema.GetPromptResult(
                "Register a new pet, step by step",
                List.of(
                        new McpSchema.PromptMessage(
                                McpSchema.Role.USER,
                                new McpSchema.TextContent(
                                        "I want to register a new pet.")),
                        new McpSchema.PromptMessage(
                                McpSchema.Role.ASSISTANT,
                                new McpSchema.TextContent(
                                        "Sure! I need three things before I can register the pet:\n" +
                                                "1. Name\n" +
                                                "2. Category (e.g. Dog, Cat, Bird)\n" +
                                                "3. Status (available, pending, or sold)\n\n" +
                                                "Please provide all three, and I'll confirm the details " +
                                                "back to you before registering."))
                ));
    }
}

Desarrollo de MCP Resources

Esta sección describe el modelo de implementación de los MCP Resources en Keel, tomando el arquetipo Keel como arquitectura de referencia base para la generación de proyectos MCP Server basados en Spring AI.

Los Resources se desarrollan en el módulo <project>-mcp del proyecto generado, dentro del paquete <base-package>.mcp.resources (por ejemplo, io.github.keelframework.mcp.sample.resources).

@Resources — Implementación

Un Resource representa una fuente de información expuesta por el MCP Server para proporcionar al LLM el contexto y las reglas de negocio que necesita antes de razonar o invocar un Tool.

A diferencia de un Tool, que expone una capacidad de negocio ejecutable, un Resource no ejecuta lógica de negocio ni produce efectos secundarios: solo entrega contenido de solo lectura (estático o dinámico) que el LLM puede consultar para informarse.

Aunque su implementación es relativamente sencilla, todos los Resources deben seguir un conjunto de convenciones que garantizan una arquitectura homogénea, mantenible y consistente entre proyectos.

Keel proporciona una estructura base que estandariza la implementación de Resources, incluyendo la organización del código, la definición de la URI, la construcción del contenido devuelto y los criterios para decidir si el contenido debe ser estático o dinámico.

Aspectos técnicos de la implementación de un Resource

La implementación de un Resource involucra cinco aspectos técnicos:

# Aspecto Elemento clave Obligatorio
1 Definición del método Paquete resources, Spring bean (@Component), método que retorna McpSchema.ReadResourceResult ✅
2 Anotación @McpResource en el método uri (formato: <scheme>://<context>/<resource>), name (kebab-case: <context>-<resource>), mimeType y description con el formato: QUÉ CONTIENE / CUÁNDO CONSULTARLO ✅
3 Construcción del contenido List<McpSchema.TextResourceContents> dentro del ReadResourceResult, con la misma uri y mimeType declarados en la anotación ✅
4 Origen del contenido Estático (constante en código, no cambia) vs dinámico (obtenido del backend en tiempo real a través de McpRestClient, siempre de solo lectura) ✅
5 Convención de nombres del método Verbo + sustantivo, camelCase (ej. getProductRules) ✅

Los Resources anotados con @McpResource se registran automáticamente a través de spring.ai.mcp.server.annotation-scanner.enabled: true, igual que los Prompts.

Estructura estándar de un Resource

Cada Resource desarrollado sobre Keel debe seguir una estructura común que facilite su comprensión tanto para los desarrolladores como para el LLM. Esta estructura incluye la definición del método, la descripción funcional a través de la anotación @McpResource, la construcción del contenido de respuesta (McpSchema.ReadResourceResult) que expone la información al LLM, y la definición explícita del origen del contenido (estático o dinámico) y su formato.

El siguiente ejemplo muestra la estructura recomendada:


package io.keelframework.sample.mcp.resources;
@Component
public class PetstoreResources {

    @McpResource(
            uri = "petstore://tools-guide",
            name = "Petstore tools guide",
            description = "Describes when and how to use each available Petstore tool. " +
                    "Consult when the LLM needs to decide which tool to call.",
            mimeType = "text/plain"
    )
    public String toolsGuide() {
        return """
                AVAILABLE TOOLS IN petstore-sample:
 
                1. findPet(petId)
                   - When to use:  the user asks about a specific pet or gives an ID
                   - Parameters:   petId (long) — numeric identifier of the pet
                   - Example:      "What's the status of pet 5?"
                   - Returns:      id, name, category and status of the pet
 
                2. listPets(status)
                   - When to use:  the user wants to browse pets by availability
                   - Parameters:   status (String) — one of: available, pending, sold
                   - Example:      "Show me all available pets"
                   - Returns:      a list of pets matching that status, plus the total count
 
                3. registerPet(name, category, status)
                   - When to use:  the user wants to add a new pet to the store
                   - Parameters:   name (String), category (String), status (String, one of:
                                   available, pending, sold)
                   - Example:      "Register a new dog named Rex, available"
                   - Returns:      the newly registered pet, including its assigned ID
 
                GENERAL RULES:
                - Never invent pet data — always use the tools for real information
                - If a required parameter is missing, ask the user before calling the tool
                - "status" only accepts: available, pending, sold — reject anything else
                  and ask the user to pick one of these three values
                """;
    }

}

Este Resource es de origen estático: su contenido está definido en código y no cambia entre invocaciones. Para un Resource dinámico (por ejemplo, el catálogo de líneas de negocio actuales), el método obtendría el contenido del backend a través de McpRestClient en cada lectura, manteniendo la misma estructura de respuesta.