In this final chapter of the Spring AI series, we’ll explore the key terms to understand when working with MCP, and provide a high-level overview of the MCP architecture. Finally, we’ll explain how to build applications that use MCP on both the client and server sides with Spring AI.

As a reminder, in case you missed any of the previous articles, you can check out the rest of the Spring AI series at the following links:

  1. Deep Learning about Spring AI: Getting Strarted
  2. Deep Learning on Spring AI: multimodularity, prompts and observability
  3. Deep Learning on Spring AI: Advisors, Structured Output and Tool Calling
  4. Deep Learning about Spring AI: RAG, Embeddings and Vector Databases
  5. Deep Learning about Spring AI: ETL and MCP

MCP Foundations

MCP is based on a client-server architecture where an application can connect to multiple servers. Within this architecture, we can identify the following components:

your computer: host with mcp client. 1. mcp protocol - mcp server A - local data soruce A. 2. mcp protocol - mcp server b - local data soruce b. 3. mcp protocol - mcp protocol server C - web APIs - remote service C (internet)

Some of the key concepts used in this protocol are:

Java and Spring

There is an SDK for Java (as well as for Python, Kotlin, Typescript, C#) that implements MCP, extended by the Spring AI MCP module, integrating Spring Boot to provide client and server starters.

The Java implementation is based on the following layered architecture:

Bottom layer: stdio, sse… first column wrapped in base, from bottom to top: MCP transport (client), MCP session, mcp client. second parallel column, from bottom to top: mcp transport (server), MCP session, mcp client

Each layer manages different responsibilities:

  1. Client/server layer: manages operations specific to each side:
  1. Session layer: manages communication patterns and state.
  2. Transport layer: handles JSON-RPC message serialization/deserialization across multiple transport implementations (stdio, SSE, or custom).
Structure of MCP Server (SSE) and MCP Server (STD IO)

MCP Client

The MCP client starter provided by Spring AI offers autoconfiguration to create an MCP client within a Spring Boot application. It also supports both synchronous and asynchronous implementations with multiple transport options. The starter provides:

Starters

The standard starter connects to one or more MCP servers simultaneously using stdio and/or SSE transports (SSE uses the HttpClient transport implementation). Each connection to an MCP server creates a new MCP client instance, and you can choose either synchronous or asynchronous clients — but not both types simultaneously.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

The WebFlux starter provides similar functionality to the standard starter but uses a WebFlux-based implementation for SSE.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>

Properties

Properties are divided into common, stdio, and SSE. Some relevant properties include:

Property Description Default
request-timeout Timeout for client requests 20s
type Server type (SYNC/ASYNC). All clients must be of a single type: sync or async SYNC
root-change-notification Enable root change notifications for all clients true
toolcallback.enabled Enable MCP tool callback integration with Spring AI tool execution true
Property Description Default
servers-configuration Resource containing the configuration of the MCP servers in JSON format -
connections Map with the different configurations by connection name for stdio -
connections.[name].command Command to run the MCP server -
connections.[name].args List of command arguments -
connections.[name].env Map of environment variables for the server process -

If you configure the servers-configuration property, you must provide the path to the file containing the configuration in Claude Desktop format (this format currently only supports configuring stdio connections). An example of the file format is:

{
  "mcpServers": {
    "filesystem": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "--mount", "type=bind,src=/home/user/test,dst=/projects/test",
        "mcp/filesystem",
        "/projects/test"
      ]
    }
  }
}
Property Description Default
connections Map with different configurations by connection name for SSE -
connections.[name].url Base URL for SSE communication with the MCP server -
connections.[name].sse-endpoint SSE endpoint to use for the connection /sse

Features

As mentioned, the starter supports two types of clients:

In addition, the starter's autoconfiguration allows for customization of various client aspects such as timeouts, event handling, or message processing. These customizations can be configured by implementing the McpSyncClientCustomizer and McpAsyncClientCustomizer classes. Some available customizations include:

@Component
public class CustomMcpSyncClient implements McpSyncClientCustomizer {
    
    @Override
    public void customize(String serverConfigurationName, McpClient.SyncSpec spec) {      
      RootCapabilities rootCapabilities = new RootCapabilities(true);
      spec.capabilities(new ClientCapabilities(null, rootCapabilities, null));
      // Sets the root URIs that this client can access.
      spec.roots(Arrays.asList(new Root(System.getProperty("user.home") + "/out", "Out foler")));
    }
}

Several transport types are supported:

Integration with the Spring AI “tool-calling” execution cycle is also possible (Tool-Calling in Spring AI), which must be enabled via the property spring.ai.mcp.client.toolcallback.enabled=true.

MCP Server

The MCP server starter provided by Spring AI offers autoconfiguration to create an MCP server in Spring Boot applications. The starter provides:

Starters

A starter must be selected based on the transport layer requirements:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>

The starter activates the McpServerAutoConfiguration class, providing:

  1. Configuration of the server's core components
  2. Handling of specifications for functions (tools), resources, and prompts
  3. Management of server capabilities and change notifications
  4. Synchronous and asynchronous implementations
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

The starter activates the McpWebMvcServerAutoConfiguration and McpServerAutoConfiguration classes, providing:

  1. HTTP transport using Spring MVC
  2. Automatic configuration of SSE endpoints
  3. STDIO transport enabled via the property spring.ai.mcp.server.stdio=true
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>

The starter activates the McpWebFluxServerAutoConfiguration and McpServerAutoConfiguration classes, providing:

  1. Reactive transport using Spring WebFlux.
  2. Automatic configuration of SSE endpoints.
  3. STDIO transport enabled via the property spring.ai.mcp.server.stdio=true

Properties

Some of the important properties for configuration, which must be set with the spring.ai.mcp.server prefix, are:

Property Description Default
stdio Enable/disable stdio transport false
type Server type (SYNC/ASYNC) SYNC
resource-change-notification Enable notifications for resource changes true
prompt-change-notification Enable notifications for prompt changes true
tool-change-notification Enable notifications for tool changes true

Types

Servers can be either synchronous or asynchronous:

Features

This starter, among other things, allows servers to expose functions, resources, and prompts to clients. It automatically converts the registered handler beans for these features into sync/async specifications based on the server type:

@Bean
ToolCallbackProvider userToolProvider(UserTools userTools) {
   return 
MethodToolCallbackProvider.builder().toolObjects(userTools).build();
}
@Bean
List<McpServerFeatures.SyncResourceSpecification> 
myResources(@Value("classpath:/static/trends.txt") Resource resource) {
    var systemInfoResource = new McpSchema.Resource("file://trends.txt", "Trends", "Fichero con 3 tendencias tecnologicas del año 2025", "text/plain", new Annotations(Arrays.asList(Role.USER), Double.valueOf("0")));
    var resourceSpecification = new 
McpServerFeatures.SyncResourceSpecification(systemInfoResource, (exchange, request) -> {
      try {
         String jsonContent = 
      resource.getContentAsString(StandardCharsets.UTF_8);
         return new McpSchema.ReadResourceResult(List.of(new 
      McpSchema.TextResourceContents(request.uri(), "text/plain", jsonContent)));
      } catch (Exception e) {
         throw new RuntimeException("Failed to generate system info", e);
      }
   });
   return List.of(resourceSpecification);
}
@Bean
List<McpServerFeatures.SyncPromptSpecification> myPrompts() {
   var prompt = new McpSchema.Prompt("search-user-by-name", "Prompt to search a user by name", List.of(new McpSchema.PromptArgument("name", "The user's name", true)));

   var promptSpecification = new 
McpServerFeatures.SyncPromptSpecification(prompt, 
      (exchange, getPromptRequest) -> {
         String nameArgument = (String) 
      getPromptRequest.arguments().get("name");
         if (nameArgument == null) {nameArgument = "friend";}
         var userMessage = new PromptMessage(Role.USER, new 
      TextContent("Existe el usuario " + nameArgument + "?"));
         return new GetPromptResult("Prompt to search a user by name", List.of(userMessage));
      });        
      return List.of(promptSpecification);
}
@Bean
BiConsumer<McpSyncServerExchange, List<McpSchema.Root>> rootsChangeHandler(){
    return (exchange, roots) -> {
        log.info("Cambio en los roots recibido: {}", roots);
    };
}

In reference to the transport types, those provided by these starters are:

Demo

To verify how both starters work, we create two projects/applications, one for the client and one for the server:

  1. Client: through property configuration, we specify two servers:
spring:
  ...
    mcp:
      client:
        toolcallback:
          enabled: true
        type: SYNC
        sse:
          connections:
            usuarios:
              url: http://localhost:8081
        stdio:
          connections:
            files:
              command: docker
              args:
                - 'run'
                - '-i'
                - '--rm'
                - '--mount'
                - 'type=bind,src=/home/user/test,dst=/projects/test'
                - 'mcp/filesystem'
                - '/projects/test'
  1. Server: provides a function (tool) to retrieve a list of users, and also exposes resources and prompts.

To run the endpoints, you must start the MCP server application first, followed by the MCP client application (the file system server starts automatically via Docker when the MCP client is launched).

In the app acting as MCP client, the following endpoints are exposed:

You are in the directory '/projects/test'. The files 'README.md', 'code.txt', and 'hola' are in this folder.
Same file listing via command
Available users: Jose, Lucia, David, Marga. Do you need anything else?
"name": "listUsers", "description": "returns a list of users", "inputSchema": { "type": "object", "properties": 13, "required": [], "additionalProperties": false }
"resources":  "uri": "file://trends.txt", "name": "Trends", "description": "File with 3 technology trends for 2025", "mimeType": "text/plain", "annotations": { "audience":  "user", "priority": 0.0 } }
"contents":  "uri": "file://trends.txt", "mimeType": "text/plain", "text": "1- Voice and video in AI\n2 - Accessibility: building inclusive applications\n3 - From Digital Native to AI Native: the new era of AI in business"
"prompts":  "name": "search-user-by-name", "description": "Prompt to search a user by name", "arguments":  "name": "name", "description": "The user's name", "required": true
"role": "user", "content": { "type": "text", "text": "Does user Juan exist?" }
Root change received: Root uri=/home/simonrodriguez/springai, name=Spring AI folder, Root uri=/home/simonrodriguez/out, name=Out folder

Example code for both client and server MCP is available at the provided links.

Conclusion

This concludes the final chapter in the Spring AI series. In this case, we’ve explored key MCP concepts as well as verified what functionality the MCP starters offer with an implementation example.

Throughout the Spring AI series, we’ve seen how to build not only traditional chat applications but also more complex apps with diverse workflows and tasks—leveraging LLMs to maintain a unified natural language interface.

Despite still being in its early versions, we can already see the power of the framework, while keeping in mind the capabilities of the LLMs we use to build AI-centered applications. It’s also important to note that the Spring team will likely continue adding new features to this module as LLMs evolve.

This is why it’s more important than ever to stay up to date with the latest developments so we don’t fall behind in this fast-paced technological era—AI is moving faster than ever. See you in the comments!

References

Tell us what you think.

Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.

Subscribe