Artículo

Explorando el Protocolo de Contexto del Modelo (MCP) con Spring AI

Explorando el Protocolo de Contexto del Modelo (MCP) con Spring AI

1. Visión general

Las aplicaciones web modernas están integrando cada vez más con los Modelos de Lenguaje Extendido (LLM) para construir soluciones, que no se limitan solo a la respuesta de preguntas basada en conocimiento general.

Para mejorar la respuesta de un modelo de IA y hacerla más contextualizada, podemos conectarlo a fuentes externas como motores de búsqueda, bases de datos y sistemas de archivos. Sin embargo, integrar y gestionar múltiples fuentes de datos con diferentes formatos y protocolos es un desafío.

El Protocolo de Contexto del Modelo (MCP), introducido por Anthropic, aborda este desafío de integración y proporciona una forma estandarizada de conectar aplicaciones impulsadas por IA con fuentes de datos externas. A través de MCP, podemos crear agentes y flujos de trabajo complejos sobre un LLM nativo.

En este tutorial, comprenderemos el concepto de MCP implementando prácticamente su arquitectura cliente-servidor con Spring AI. Crearemos un chatbot sencillo y ampliamos sus capacidades mediante servidores MCP para realizar búsquedas web, ejecutar operaciones de sistema de archivos y acceder a lógica de negocio personalizada.

2. Protocolo de Contexto de Modelo 101

Antes de sumergirnos en la implementación, echemos un vistazo más cercano a MCP y a sus diversos componentes:
Diagrama de arquitectura del protocolo de contexto del modelo (MCP) que muestra la relación entre el host, clientes, servidores y fuentes externas.

MCP sigue una arquitectura cliente-servidor que gira en torno a varios componentes clave:

  • MCP Host: es nuestra aplicación principal que se integra con un LLM y necesita conectarse con fuentes de datos externas.
  • MCP Clients: son componentes que establecen y mantienen conexiones 1:1 con los servidores MCP.
  • MCP Servers: son componentes que se integran con fuentes de datos externas y exponen funcionalidades para interactuar con ellas.
  • Herramientas: se refieren a las funciones/métodos ejecutables que los servidores MCP exponen para que los clientes los invoquen.

Además, para manejar la comunicación entre clientes y servidores, MCP ofrece dos canales de transporte.

Para habilitar la comunicación a través de flujos de entrada y salida estándar con procesos locales y herramientas de línea de comandos, proporciona el tipo de transporte Entrada/Salida Estándar (stdio). Alternativamente, para la comunicación basada en HTTP entre clientes y servidores, ofrece el tipo de transporte Eventos Enviados por el Servidor (SSE).

MCP es un tema complejo y extenso; consulte la documentación oficial para obtener más información.

3. Creando un Host MCP

Ahora que tenemos una comprensión de alto nivel de MCP, comencemos a implementar la arquitectura MCP de forma práctica.

Crearemos un chatbot utilizando el modelo Claude de Anthropic, que actuará como nuestro host MCP. Alternativamente, podemos usar un LLM local a través de Hugging Face o Ollama, ya que el modelo de IA específico es irrelevante para esta demostración.

3.1. Dependencias

Comencemos añadiendo las dependencias necesarias al archivo pom.xml de nuestro proyecto:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
    <version>1.0.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
    <version>1.0.1</version>
</dependency>

La dependencia starter de Anthropic es un envoltorio alrededor de la API de Mensajes de Anthropic, y la utilizaremos para interactuar con el modelo Claude en nuestra aplicación.

Además, importamos la dependencia starter del cliente MCP, la cual nos permitirá configurar clientes dentro de nuestra aplicación Spring Boot que mantengan conexiones 1:1 con los servidores MCP.

Dado que estamos utilizando varios starters de Spring AI en nuestro proyecto, también incluyamos el Bill of Materials (BOM) de Spring AI en nuestro pom.xml:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Con esta adición, ahora podemos eliminar la etiqueta version de ambas dependencias starter. El BOM elimina el riesgo de conflictos de versiones y garantiza que nuestras dependencias de Spring AI sean compatibles entre sí.

A continuación, configuremos la clave API de Anthropic y el modelo de chat en el archivo application.yaml:

spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-opus-4-20250514

Usamos el marcador de posición ${} para cargar el valor de nuestra clave API desde una variable de entorno.

Además, especificamos Claude 4 Opus de Anthropic, usando el ID del modelo claude-opus-4-20250514. Si lo desea, puede explorar y usar un modelo diferente según los requisitos.

Al configurar las propiedades anteriores, Spring AI crea automáticamente un bean de tipo ChatModel, lo que nos permite interactuar con el modelo especificado.

3.2. Configuración de Clientes MCP para Servidores de Búsqueda Brave y de Sistema de Archivos

Ahora, configuraremos clientes MCP para dos implementaciones de servidor MCP preconstruidas: Brave Search y Filesystem. Estos servidores permitirán que nuestro chatbot realice búsquedas web y operaciones de sistema de archivos.

Comencemos registrando un cliente MCP para el servidor MCP de búsqueda Brave en el archivo application.yaml:

spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            brave-search:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-brave-search"
              env:
                BRAVE_API_KEY: ${BRAVE_API_KEY}

Aquí, configuramos un cliente con transporte stdio. Especificamos el comando npx para descargar y ejecutar el paquete basado en TypeScript @modelcontextprotocol/server-brave-search y usamos la bandera -y para confirmar todas las indicaciones de instalación.

Además, proporcionamos la variable de entorno BRAVE_API_KEY.

A continuación, configuremos un cliente MCP para el servidor MCP de sistema de archivos:

spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            filesystem:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-filesystem"
                - "./"

Al igual que la configuración anterior, especificamos el command y los argumentos necesarios para ejecutar el paquete del servidor MCP de sistema de archivos. Esta configuración permite que nuestro chatbot realice operaciones como crear, leer y escribir archivos en el directorio especificado.

En este caso, solo configuramos el directorio actual (./) para ser utilizado en las operaciones de sistema de archivos; sin embargo, podemos especificar varios directorios añadiéndolos a la lista args.

Al iniciar la aplicación, Spring AI escaneará nuestras configuraciones, creará los clientes MCP y establecerá conexiones con sus respectivos servidores MCP. También crea un bean de tipo SyncMcpToolCallbackProvider, que proporciona una lista de todas las herramientas expuestas por los servidores MCP configurados.

3.3. Construyendo un Chatbot Básico

Con nuestro modelo de IA y los clientes MCP configurados, construyamos un chatbot sencillo:

@Bean
ChatClient chatClient(ChatModel chatModel, SyncMcpToolCallbackProvider toolCallbackProvider) {
    return ChatClient
      .builder(chatModel)
      .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks())
      .build();
}

Comenzamos creando un bean de tipo ChatClient usando los beans ChatModel y SyncMcpToolCallbackProvider. La clase ChatClient actuará como nuestro punto de entrada principal para interactuar con nuestro modelo de finalización de chat, es decir, Claude 4 Opus.

A continuación, inyectemos el bean ChatClient para crear una nueva clase ChatbotService:

String chat(String question) {
    return chatClient
      .prompt()
      .user(question)
      .call()
      .content();
}

Creamos un método chat() donde pasamos la question del usuario al bean chat client y simplemente devolvemos la respuesta del modelo de IA.

Ahora que hemos implementado nuestra capa de servicio, expondremos una API REST sobre ella:

@PostMapping("/chat")
ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
    String answer = chatbotService.chat(chatRequest.question());
    return ResponseEntity.ok(new ChatResponse(answer));
}

record ChatRequest(String question) {}

record ChatResponse(String answer) {}

Usaremos el punto final de API anterior para interactuar con nuestro chatbot más adelante en el tutorial.

4. Creando un Servidor MCP Personalizado

Además de usar servidores MCP preconstruidos, podemos crear nuestros propios servidores MCP para ampliar las capacidades de nuestro chatbot con nuestra lógica de negocio.

Exploraremos cómo crear un servidor MCP personalizado usando Spring AI.

Crearemos una nueva aplicación Spring Boot en esta sección.

4.1. Dependencias

Primero, incluyamos la dependencia necesaria en nuestro archivo pom.xml:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
    <version>1.0.0</version>
</dependency>

Importamos la dependencia del servidor MCP de Spring AI, que proporciona las clases necesarias para crear un servidor MCP personalizado que admita el transporte SSE basado en HTTP.

4.2. Definición y Exposición de Herramientas Personalizadas

A continuación, definamos algunas herramientas personalizadas que nuestro servidor MCP expondrá.

Crearemos una clase AuthorRepository que proporcione métodos para obtener detalles de autores:

class AuthorRepository {
    @Tool(description = "Get CodeJa author details using an article title")
    Author getAuthorByArticleTitle(String articleTitle) {
        return new Author("John Doe", "/cdn-cgi/l/email-protection");
    }

    @Tool(description = "Get highest rated CodeJa authors")
    List<Author> getTopAuthors() {
        return List.of(
          new Author("John Doe", "/cdn-cgi/l/email-protection"),
          new Author("Jane Doe", "/cdn-cgi/l/email-protection")
        );
    }

    record Author(String name, String email) {
    }
}

Para nuestra demostración, devolvemos detalles de autores codificados de manera fija; en una aplicación real, las herramientas típicamente interactuarían con una base de datos o una API externa.

Annotamos nuestros dos métodos con la anotación @Tool y proporcionamos una breve description para cada uno. La description ayuda al modelo de IA a decidir si y cuándo llamar a las herramientas en función de la entrada del usuario e incorporar el resultado en su respuesta.

A continuación, registramos nuestras herramientas de autor con el servidor MCP:

@Bean
ToolCallbackProvider authorTools() {
    return MethodToolCallbackProvider
      .builder()
      .toolObjects(new AuthorRepository())
      .build();
}

Usamos el MethodToolCallbackProvider para crear un bean ToolCallbackProvider a partir de las herramientas definidas en nuestra clase AuthorRepository. Los métodos anotados con @Tool se expondrán como herramientas MCP cuando la aplicación se inicie.

Alternativamente, podemos registrar herramientas dinámicamente en tiempo de ejecución según condiciones específicas:

@Bean
CommandLineRunner commandLineRunner(
    McpSyncServer mcpSyncServer,
    @Value("${com.codeja.author-tools.enabled:false}") boolean authorToolsEnabled
) {
    return args -> {
        if (authorToolsEnabled) {
            ToolCallback[] toolCallbacks = ToolCallbacks.from(new AuthorRepository());
            List<SyncToolSpecification> tools = McpToolUtils.toSyncToolSpecifications(toolCallbacks);
            tools.forEach(tool -> {
                mcpSyncServer.addTool(tool);
                mcpSyncServer.notifyToolsListChanged();
            });
        }
    };
}

Aquí, inyectamos el bean McpSyncServer, creado automáticamente por Spring AI, para registrar condicionalmente nuestras herramientas según una propiedad de configuración. Al igual que addTool(), la clase McpSyncServer también ofrece el método removeTool() para eliminar ciertas herramientas.

Para nuestra demostración, utilizamos la interfaz CommandLineRunner; sin embargo, podemos agregar/eliminar herramientas cuando se invoque una API REST o en respuesta a un evento de la aplicación. Esto es particularmente útil para habilitar o deshabilitar funciones según los permisos del usuario, niveles de suscripción u otra lógica de negocio.

4.3. Configuración de un Cliente MCP para Nuestro Servidor MCP Personalizado

Finalmente, para usar nuestro servidor MCP personalizado en nuestra aplicación de chatbot, necesitamos configurar un cliente MCP contra él:

spring:
  ai:
    mcp:
      client:
        sse:
          connections:
            author-tools-server:
              url: http://localhost:8081

En el archivo application.yaml, configuramos un nuevo cliente contra nuestro servidor MCP personalizado. Observe que estamos usando el tipo de transporte SSE aquí.

Esta configuración asume que el servidor MCP se ejecuta en http://localhost:8081. Debemos asegurarnos de actualizar la url si se ejecuta en otro host o puerto.

Además, si hemos habilitado que nuestro servidor MCP agregue o elimine herramientas en tiempo de ejecución dinámicamente, podemos registrar un listener para detectar estos cambios de herramientas:

@Bean
McpSyncClientCustomizer mcpSyncClientCustomizer() {
    return (name, mcpClientSpec) -> {
        mcpClientSpec.toolsChangeConsumer(tools -> {
            logger.info("Detected tools changes.");
        });
    };
}

Aquí, definimos un bean de tipo McpSyncClientCustomizer y registramos un listener usando el método toolsChangeConsumer(). Si bien simplemente registramos aquí para la simplicidad, en una aplicación real, podríamos refrescar el bean ChatClient o reiniciar programáticamente nuestra aplicación.

Con esta configuración, nuestro cliente MCP ahora puede invocar las herramientas expuestas por nuestro servidor personalizado, además de las herramientas proporcionadas por los servidores MCP de Brave Search y Filesystem.

5. Interactuando con Nuestro Chatbot

Ahora que hemos construido nuestro chatbot e integrado varios servidores MCP, interactuemos con él y probémoslo.

Usaremos la CLI HTTPie para invocar el punto final de API del chatbot:

http POST :8080/chat question="How much was Elon Musk's initial offer to buy OpenAI in 2025?"

Aquí, enviamos una simple question al chatbot sobre un evento que ocurrió después de la fecha de corte de conocimiento del LLM. Veamos qué obtenemos como respuesta:

{
    "answer": "Elon Musk's initial offer to buy OpenAI was $97.4 billion. [Source](https://www.reuters.com/technology/openai-board-rejects-musks-974-billion-offer-2025-02-14/)."
}

Como se puede ver, el chatbot pudo realizar una búsqueda web usando el servidor MCP de búsqueda Brave configurado y proporcionar una respuesta precisa junto con una fuente.

A continuación, verifiquemos que el chatbot pueda realizar operaciones de sistema de archivos usando el servidor MCP de sistema de archivos:

http POST :8080/chat question="Create a text file named 'mcp-demo.txt' with content 'This is awesome!'."

Instruimos al chatbot para crear un archivo mcp-demo.txt con cierto contenido. Veamos si logra cumplir la solicitud:

{
    "answer": "The text file named 'mcp-demo.txt' has been successfully created with the content you specified."
}

El chatbot responde con una respuesta exitosa. Podemos verificar que el archivo se creó en el directorio que especificamos en el archivo application.yaml.

Finalmente, verifiquemos si el chatbot puede llamar a una de las herramientas expuestas por nuestro servidor MCP personalizado. Preguntaremos sobre los detalles del autor mencionando un título de artículo:

http POST :8080/chat question="Who wrote the article 'Testing CORS in Spring Boot?' on CodeJa, and how can I contact them?"

Invocamos la API y veamos si la respuesta del chatbot contiene los detalles del autor codificados:

{
    "answer": "The article 'Testing CORS in Spring Boot' on CodeJa was written by John Doe. You can contact him via email at [/cdn-cgi/l/email-protection](mailto:/cdn-cgi/l/email-protection)."
}

La respuesta anterior verifica que el chatbot obtenga los detalles del autor usando la herramienta getAuthorByArticleTitle() que nuestro servidor MCP personalizado expone.

Recomendamos encarecidamente configurar el código localmente y experimentar con el chatbot usando diferentes prompts.

6. Conclusión

En este artículo, hemos explorado el Protocolo de Contexto del Modelo e implementado su arquitectura cliente-servidor usando Spring AI.

Primero, construimos un chatbot sencillo utilizando el modelo Claude 4 Opus de Anthropic para actuar como nuestro host MCP.

Luego, para proporcionar al chatbot capacidad de búsqueda web y habilitar la ejecución de operaciones de sistema de archivos, configuramos clientes MCP contra implementaciones preconstruidas de servidores MCP de la API de búsqueda Brave y del sistema de archivos.

Finalmente, creamos un servidor MCP personalizado y configuramos su cliente MCP correspondiente dentro de nuestra aplicación host MCP.

Newsletter Semanal de Java

Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.

Sin spam. Cancela cuando quieras.

Compartir artículo