Skip to main content
Article

Model Context Protocol (MCP) with Spring AI: Exposing and Consuming Remote Tools

The Model Context Protocol (MCP) is an open protocol that standardizes communication between AI applications and external tools. In this article, we build an MCP Server that exposes a RAG tool and an MCP Host that orchestrates it alongside the GitHub MCP server.

8 min read
spring-aiiallmjavaspring-bootmcp
spring-aiiallm

Until now, our AI tools were defined locally within the same application. But in a real ecosystem, tools can reside in remote services: a specification server, a GitHub API, a database service, etc.

The Model Context Protocol (MCP) is an open protocol, initiated by Anthropic, that standardizes communication between AI applications (hosts) and tool providers (servers). Spring AI offers native MCP support.

In this article, we explore the two sub-modules of the mcp module:

  • mcp-server: exposes a RAG tool via the MCP protocol (stdio)
  • mcp-host: orchestrates GPT-4o with multiple MCP servers

A- MCP Architecture

In an MCP architecture with Spring AI, three main roles interact:

RoleDescriptionExample
HostAI application that consumes toolsOur Spring AI application
ServerService that exposes toolsRAG server, GitHub MCP server
ClientCommunication component in the hostMcpSyncClient

B- The MCP Server: Exposing a RAG Tool

The MCP Server exposes a RAG search tool via the stdio protocol.

Dependencies

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-rag</artifactId>
</dependency>

Configuration

spring:
  main:
    banner-mode: off
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
    vectorstore:
      pgvector:
        dimensions: 1536
        initialize-schema: true
        table-name: spec_store
    mcp:
      server:
        name: specifications-mcp-server
        stdio: true
  datasource:
    url: jdbc:postgresql://localhost:5438/demo_db
    username: demo
    password: demo
server:
  port: 0
logging:
  pattern:
    console:

Key configuration points:

  • mcp.server.stdio: true: the server communicates via stdin/stdout (not HTTP)
  • server.port: 0: no HTTP port (stdio communication only)
  • banner-mode: off and empty logging.pattern.console: avoids polluting stdout
  • dimensions: 1536: OpenAI vector dimensions

The Exposed RAG Tool

@Component
public class RagTools {
 
    @Autowired @Lazy
    private RagService ragService;
 
    @Tool(description = "Retrieves extracts from application "
        + "specifications relevant to a given query.")
    public String retrievesExtractsFromSpecifications(
            String input) {
        return ragService.run(input);
    }
}

The @Tool annotation exposes the method as an MCP tool. The RagService combines query rewriting and multi-query expansion for optimized searches.

MCP Tool Registration

@Configuration(proxyBeanMethods = false)
public class McpServerConfig {
 
    @Bean
    ToolCallbackProvider systemToolsProvider(RagTools tools) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(tools)
                .build();
    }
}

The MethodToolCallbackProvider scans @Tool-annotated methods and exposes them via the MCP protocol.

The Underlying RagService

@Component
public class RagService {
 
    public String run(String input) {
        var advisor = RetrievalAugmentationAdvisor.builder()
                .queryTransformers(
                    RewriteQueryTransformer.builder()
                        .chatClientBuilder(chatClient.mutate())
                        .promptTemplate(
                            new PromptTemplate(rewritePrompt))
                        .build())
                .queryExpander(
                    MultiQueryExpander.builder()
                        .chatClientBuilder(chatClient.mutate())
                        .build())
                .documentRetriever(
                    VectorStoreDocumentRetriever.builder()
                        .vectorStore(vectorStore)
                        .build())
                .build();
 
        return chatClient.prompt()
                .advisors(advisor)
                .user(input)
                .call()
                .content();
    }
}

C- The MCP Host: Orchestrating Multiple Servers

The MCP Host is the main application that consumes tools from multiple MCP servers.

Dependencies

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

MCP Server Configuration

Server configuration is in mcp-servers-config.json:

{
  "mcpServers": {
    "app-specification": {
      "command": "java",
      "args": [
        "--enable-native-access=ALL-UNNAMED",
        "-jar",
        "/path/to/mcp-server-0.1.0-SNAPSHOT.jar"
      ]
    },
    "github": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {}
    }
  }
}

Two MCP servers are configured:

  • app-specification: our Java RAG server, launched as a jar
  • github: GitHub's official MCP server, run via Docker

Spring Configuration

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o
    mcp:
      client:
        stdio:
          servers-configuration:
            classpath:/mcp-servers-config.json

The Host Controller

@RestController
@RequestMapping("/chat")
public class DemoController {
 
    private final ChatClient chatClient;
 
    public DemoController(
            ChatClient.Builder chatClientBuilder,
            List<McpSyncClient> mcpSyncClients) {
        this.chatClient = chatClientBuilder
                .defaultSystem("""
                    You are a code assistant and can create
                    projects on GitHub using the defined
                    specifications.
                    """)
                .defaultToolCallbacks(
                    SyncMcpToolCallbackProvider.builder()
                        .mcpClients(mcpSyncClients)
                        .build())
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }
 
    @GetMapping
    public String rag(String message) {
        return chatClient.prompt(message).call().content();
    }
}

Key Points

  • List<McpSyncClient>: Spring AI auto-configures one MCP client per declared server
  • SyncMcpToolCallbackProvider: exposes all MCP server tools to the ChatClient
  • The GPT-4o model sees all tools (RAG + GitHub) and can combine them
  • The system prompt guides the assistant on its role

D- Complete Execution Flow

When the user asks a question:

1. "Create a GitHub repo with the project specs"

2. GPT-4o identifies the needed tools:
   - retrievesExtractsFromSpecifications (RAG)
   - create_repository (GitHub MCP)

3. Spring AI calls the MCP RAG Server via stdio
   → RAG server searches PGVector
   → Returns relevant specifications

4. Spring AI calls the GitHub MCP Server via Docker
   → Creates the repository with specs

5. GPT-4o aggregates and generates the final response

E- Supported MCP Transports

TransportDescriptionUse Case
StdioCommunication via stdin/stdoutLocal processes, jars, CLI
Streamable-HTTPHTTP streaming transport for MCP exchangesRemote services, web integrations
Stateless Streamable-HTTPStateless HTTP variant for independent requestsStateless environments, horizontal scaling
SSEServer-Sent Events (HTTP)Simple server-side event streaming

The Stdio transport is ideal for local servers like in our example, while Streamable-HTTP, Stateless Streamable-HTTP, and SSE cover remote HTTP exposure depending on the required statefulness and streaming model.

Conclusion

The Model Context Protocol standardizes the AI tool ecosystem. With Spring AI, integrating MCP servers is as simple as adding a JSON configuration file.

Key takeaways:

  • An MCP Server exposes tools via @Tool + ToolCallbackProvider
  • An MCP Host consumes tools via McpSyncClient + SyncMcpToolCallbackProvider
  • Stdio, Streamable-HTTP, Stateless Streamable-HTTP, and SSE cover the main MCP integration modes
  • Multiple MCP servers (RAG, GitHub, etc.) can be combined in a single host

This article concludes the Spring AI in Action series. We have covered all framework features: ChatClient, memory, RAG, tools, agents, and MCP. The Spring AI ecosystem gives Java developers the tools needed to build AI applications.

I hope you found this series useful. Thank you for following along.

To learn more:


"Spring AI in Action" Series

  1. Introduction to Spring AI
  2. ChatClient API: Getting Started with the API
  3. Chat Memory: Conversational Context
  4. RAG: Ingestion Pipeline
  5. RAG: From Naive to Advanced
  6. Function Calling
  7. Tools + Security
  8. Multi-Agent Orchestration
  9. Model Context Protocol (MCP)
ShareXLinkedIn