Model Context Protocol (MCP) Basics and building MCP Server and Client in Java and Spring

    Model Context Protocol (MCP) Basics and building MCP Server and Client in Java and Spring

    22/05/2025

    Introduction

    Model Context Protocol (MCP) is a major buzzword in the AI world these days. MCP is an open protocol designed to standardize how applications deliver context to LLMs. Imagine MCP as the USB-C port for AI applications: just as USB-C offers a universal way to connect devices to a wide range of peripherals, MCP enables a consistent and flexible method for linking AI models with diverse data sources and tools.

    Before we jump into the details of MCP, let's take a step back and understand how we used to build AI applications before Anthropic introduced MCP.

    How does an Agent Work?

    AI Agent AI Framework LangChain, Spring AI, AI SDK etc LLM Retrieval Tools Memory Internal APIs Databases Web APIs File Systems Integrate Response Query Results Call Response Write Read

    An AI agent is essentially an LLM with extended capabilities through three key components that allow it to overcome the limitations and interact with the external world:

    1. Retrieval: Enables the LLM to query external knowledge bases, documents, or databases to access information beyond its training data. This creates a more up-to-date and accurate response system.

    2. Tools: Allows the LLM to interact with external systems, APIs, databases, web services, file systems, external devices etc. Tools give the model the ability to perform actions like searching the web, running code, accessing specialized databases, or controlling other applications.

    3. Memory: Provides persistence across interactions, allowing the agent to remember past conversations and maintain context over time. This creates more coherent, consistent experiences for users.

    These components work together to transform a static LLM into a dynamic agent that can access information, perform actions in the external world, and maintain contextual awareness throughout interactions.

    Why MCP?

    Before Anthropic came with the idea of the Model Context Protocol, most of the agent applications were using separate custom tools, prompts and data access which was closely coupled with the code. There was no standard way to share these which meant there was too much of duplicate work even within the same organization.

    Agent 1 Custom Prompts Custom Retrieval Custom Tools Agent 2 Custom Prompts Custom Retrieval Custom Tools Agent 3 Custom Prompts Custom Retrieval Custom Tools Duplication of Effort Each agent uses custom implementations of the same components No standardized way to share across projects or teams

    This led to:

    • Wasted Development Resources: Teams duplicated effort building similar components
    • Inconsistent User Experiences: Different agents behaved differently even when performing similar tasks
    • Limited Interoperability: Agents couldn't easily share context or capabilities
    • High Maintenance Overhead: Each custom implementation required its own maintenance and updates

    How MCP Solves the Problem

    MCP addresses the duplication problem by providing a standardized protocol that enables sharing and reuse of tools, prompts, and retrieval systems across different AI agents. This allows teams to build common tools in MCP servers and then use them across many applications, significantly reducing development time and effort.

    AI Agent 1 No custom implementation needed AI Agent 2 No custom implementation needed AI Agent 3 No custom implementation needed MCP Server 1 Prompts Resources MCP Server 2 Tools Resources Standardized Reusable Components All agents access the same prompts, resources, tools and functions No duplication of development effort across applications

    MCP transforms the fragmented AI development landscape by providing a standardized protocol that enables component sharing across applications. Here's how it solves the key problems:

    1. Standardized Prompt Management: Instead of each application implementing custom prompts, MCP establishes shared prompts that can be accessed by all AI agents, ensuring consistent behavior and eliminating duplicate prompt engineering efforts.

    2. Unified Retrieval Systems: MCP allows multiple agents to access the same resources and retrieval services, providing standardized access to knowledge bases and data sources. Teams no longer need to build separate retrieval implementations.

    3. Shared Tool Ecosystem: Common tools like code execution, API integrations, and data processing capabilities can be developed once and accessed through the MCP protocol by any agent. This creates a rich ecosystem of reusable components. Ever since the introduction of MCP, open-source communities have started building a lot of tools and resources that can be used with MCP.

    How MCP Works : Key Components

    LLM MCP Host Claude Desktop GitHub Copilot AI Agent MCP Client MCP Client MCP Client MCP Server MCP Server MCP Server Local Filesystem Database Web APIs (Youtube, Github ,Slack etc.) MCP Protocol

    Below are the key components of the MCP architecture:

    • MCP Host: AI agents like Claude Desktop, GitHub Copilot or a Travel Assistant which wants to access tools, resources etc via the MCP protocol. MCP host will access the MCP server and interact with LLMs.
    • MCP Client: The client-side implementation of the MCP protocol, which allows AI agents to communicate with the MCP server. It handles requests and responses, ensuring smooth interaction with the server. This runs alongside the AI agent.
    • MCP Server: Programs that expose their capabilities via the MCP protocol. These can be custom APIs in the organization which interacts with databases, third-party APIs etc

    How MCP Works: Client-Server Communication

    MCP Client In MCP Host AI Agent MCP Server Prompt Resources Tools & Functions Transport Layer JSON-RPC 2.0 over stdio or SSE {"jsonrpc": "2.0", "method": "generate", "params": {...}} {"jsonrpc": "2.0", "result": "content", "id": 123}

    MCP client communicates with the MCP server using JSON-RPC 2.0, a lightweight remote procedure call (RPC) protocol. This allows for easy integration and communication between different components of the architecture.

    Transport Types

    • 1. Standard input/output (stdio): This allows for local processes to communicate directly with the MCP server, facilitating quick and efficient data exchange. An example of this is local file system MCP server which can answer questions about local files.
    • 2. HTTP with Server-Sent Events (SSE): In this MCP server operates as an independent process that can handle multiple client connections. MCP server will have 2 endpoints:
      • SSE endpoint: for clients to establish a connection and receive messages from the server
      • HTTP Post endpoint: for clients to send messages to the server

    HTTP with SSE is now getting replaced with Streamable HTTP and there are some implementations already.

    MCP Server Capabilities

    • Prompts: Reusable prompt templates that can be shared across different AI agents
    • Resources: Handles files, documents, and data sources that provide context
    • Tools: Functions that can be called by LLM.

    Connection Lifecycle

    MCP Connection Lifecycle MCP Host MCP Server Config File AI Model 1. Read configuration file 2. initialize request {version, capabilities} 3. initialize response {version, capabilities, tools} 4. initialized notification Connection Ready 6. Present capabilities to AI model Persistent Connection (Session Duration) 8. Close connection

    The MCP connection lifecycle follows a structured protocol:

    1. The MCP host reads its configuration file to determine which MCP servers to connect to.

    2. The host sends an initialize request to the MCP server, including protocol version and capabilities.

    3. The server responds with an initialize response containing its protocol version and available capabilities - tools, data endpoints, and supported actions.

    4. The host acknowledges with an initialized notification, completing the handshake.

    5. The connection becomes ready for use, establishing a communication channel for the duration of the session.

    6. The host presents the discovered capabilities to the AI model, expanding its available functions.

    7. The connection remains open persistently throughout the agent's session, supporting stateful interactions.

    8. When the session ends, the host gracefully closes the connection.

    Creating MCP server and client in Java

    There are 2 options to create an MCP server in Java:

    1. Using the MCP Java SDK: This SDK contributed by Spring AI team enables Java applications to interact with AI models and tools through a standardized interface, supporting both synchronous and asynchronous communication patterns.
    2. Spring AI MCP: Extends the MCP Java SDK with Spring Boot integration, providing both client and server starters for easy setup and configuration.

    In this post, we will use Spring AI MCP.

    What we will build

    First we will build an MCP server for youtube search and transcript and integrate it with MCP host like Claude Desktop or Github Copilot in VSCode.

    Then we will create an MCP client as part of Learning Assistant AI agent and integrate our youtube MCP server with it. Also we will integrate some open source MCP servers available like local filesystem, web search etc.

    MCP Hosts Claude Desktop Claude AI Assistant VS Code Copilot Code Assistant Learning Assistant Spring AI + OpenAI MCP Client MCP Client MCP Client Spring AI MCP File System MCP Server Brave Search MCP Server YT MCP Server Spring AI MCP Local File System Web Search YouTube Data API Search and Transcript MCP Protocol

    Build Youtube MCP Server

    Let us head to Spring Initializr and create a new Spring Boot project with the following dependencies:

    • Spring Web
    • Model Context Protocol Server

    If you just want stdio transport, you will not need to add Spring Web dependency.

    First we will create a common component for invoking the Youtube Data API. This will be used by other components like YoutubeSearchService.

    @Service public class YoutubeApiClient { private static final Logger logger = LoggerFactory.getLogger(YoutubeApiClient.class); private final String apiKey; private final RestClient restClient; private static final String API_BASE_URL = "https://www.googleapis.com/youtube/v3"; public YoutubeApiClient(@Value("${youtube.api.key}") String apiKey) { this.apiKey = apiKey; this.restClient = RestClient.builder().build(); } public String get(String endpoint, Map<String, Object> params) { UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(API_BASE_URL + endpoint) .queryParam("key", apiKey); if (params != null) { params.forEach(builder::queryParam); } String url = builder.toUriString(); logger.info("[YoutubeApiClient] Sending GET request: {}", url); ResponseEntity<String> response = restClient.get().uri(url).retrieve().toEntity(String.class); logger.info("[YoutubeApiClient] Response: {}", response.getBody()); return response.getBody(); } }

    Here we are using RestClient to make the API calls to Youtube Data API. Along with the parameters we are also passing the API key as a query parameter.

    Now let us add the below entries to application.properties file:

    spring.application.name=youtube-mcp youtube.api.key=${YOUTUBE_API_KEY} # For integration using stdio enable the below #spring.ai.mcp.server.stdio=true #spring.main.banner-mode=off #logging.pattern.console= #logging.file.name=youtube-mcp-spring.log

    You can get the API key from Google Cloud Console and set the YOUTUBE_API_KEY environment variable to your Youtube Data API key.

    Now let us add a YoutubeVideoSearchService component which will use the YoutubeApiClient to search for videos.

    Also we will add a @Tool annotation to this component which will be used by the MCP server to expose this functionality as tool.

    @Service public class YoutubeVideoSearchService { private final YoutubeApiClient apiClient; public YoutubeVideoSearchService(YoutubeApiClient apiClient) { this.apiClient = apiClient; } @Tool( name = "youtube_search", description = "Search for videos on YouTube using a query string." ) public String searchVideos(String query, int maxResults) { Map<String, Object> params = new HashMap<>(); params.put("part", "snippet"); params.put("q", query); params.put("maxResults", maxResults); return apiClient.get("/search", params); } }

    Similarly we can add other components to call other Youtube Data API endpoints following the same approach. You can find the code for those here youtube-mcp-server.

    For the transcript service we will use a JavaScript library youtube-transcript.

    Let us create a JavaScript file transcript.js and add the following code:

    const { YoutubeTranscript } = require('youtube-transcript'); async function main() { const videoId = process.argv[2]; if (!videoId) { console.log(JSON.stringify({ error: 'Usage: node get_transcript.js <videoId>' })); process.exit(1); } try { const transcript = await YoutubeTranscript.fetchTranscript(videoId); console.log(`# Transcript for YouTube Video: ${videoId}\n`); transcript.forEach((entry, index) => { const cleanText = entry.text.replace(/&amp;#39;/g, "'").replace(/&amp;#34;/g, '"'); const timestamp = formatTimestamp(entry.offset); console.log(`[${timestamp}] : ${cleanText}`); }); } catch (error) { console.log(JSON.stringify({ error: error.message })); } } function formatTimestamp(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; } main();

    Also initialize the JavaScript project and install the youtube-transcript library.

    npm init -y npm install youtube-transcript

    Now we will create a YoutubeTranscriptionService component which will use the above JavaScript file to get the transcript for a video.

    @Service public class YoutubeTranscriptionService { @Tool( name = "youtube_transcript", description = "Get transcript for a YouTube video by video ID." ) public String getTranscript(String videoId) { try { ProcessBuilder pb = new ProcessBuilder( "node", "get_transcript.js", videoId ); pb.redirectErrorStream(true); Process process = pb.start(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String output = reader.lines().collect(Collectors.joining("\n")); int exitCode = process.waitFor(); return output; } } catch (Exception e) { return "{\"error\": \"" + e.getMessage().replace("\"", "'") + "\"}"; } } }

    Now we have all the tools, let us expose them in our MCP server. For that we just need to add a @ToolCallbackProvider bean and add all the tools to it.

    @SpringBootApplication public class YoutubeMcpApplication { public static void main(String[] args) { SpringApplication.run(YoutubeMcpApplication.class, args); } @Bean public ToolCallbackProvider youtubeToolsCallbackProvider( YoutubeActivitiesService youtubeActivitiesService, YoutubeChannelService youtubeChannelService, YoutubeCommentsService youtubeCommentsService, YoutubeMetaService youtubeMetaService, YoutubePlaylistItemsService youtubePlaylistItemsService, YoutubePlaylistService youtubePlaylistService, YoutubeThumbnailsService youtubeThumbnailsService, YoutubeVideoDetailsService youtubeVideoDetailsService, YoutubeVideoSearchService youtubeVideoSearchService, YoutubeChannelSectionsService youtubeChannelSectionsService, YoutubeTranscriptionService youtubeTranscriptionService ) { var toolCallbackProvider = MethodToolCallbackProvider.builder() .toolObjects( youtubeVideoSearchService, youtubeTranscriptionService, youtubeActivitiesService, youtubeChannelService, youtubeCommentsService, youtubeMetaService, youtubePlaylistItemsService, youtubePlaylistService, youtubeThumbnailsService, youtubeVideoDetailsService, youtubeChannelSectionsService ) .build(); return toolCallbackProvider; } }

    Now we can start our MCP server.

    Testing the MCP server

    You can test it from any MCP host like Claude Desktop or Github Copilot in VSCode.

    In VSCode, we can add the below configuration in settings.json file to connect to the MCP servers.

    "mcp": { "servers": { "filesystem": { "type": "stdio", "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/Shared/tutorials" ] }, "brave-search": { "type": "stdio", "command": "docker", "args": ["run", "-i", "--rm", "-e", "BRAVE_API_KEY", "mcp/brave-search"], "env": { "BRAVE_API_KEY": "api key" } }, "youtube-spring-sse": { "type": "sse", "url": "http://localhost:8080/sse" }, }, }

    Here we are adding 3 MCP servers:

    • File System MCP server: This is a local file system MCP server which will be used to read files from local file system which will use stdio transport.
    • Brave Search MCP server: This is a Brave Search MCP server which will be used to search for web pages. This will use stdio transport and you need to set the BRAVE_API_KEY environment variable to your Brave Search API key.
    • Youtube Spring SSE MCP server: This is the Youtube MCP server we created above. MCP server is running on port 8080 and we are using SSE transport to connect to it.

    Now Github Copilot will be able to use these servers to search for files, web pages and youtube videos.

    alt text

    Running YT MCP server as a Docker container

    I have also built this as a docker image and published in docker hub. You can run it using the following command:

    docker run -e YOUTUBE_API_KEY=<your_api_key_here> -p 8085:8080 codewizard01/youtube-mcp:latest

    This will start the MCP server on port 8085.

    Running YT MCP server using stdio transport

    You can also run the MCP server using stdio transport. For that you need to add the below configuration in application.properties file:

    spring.ai.mcp.server.stdio=true spring.main.banner-mode=off logging.pattern.console= logging.file.name=youtube-mcp-spring.log

    Then you can export the jar file for youtube-mcp-server application and add the below to the mcp configuration in settings.json file:

    "mcp": { "servers": { "youtube-spring-stdio": { "type": "stdio", "command": "java", "args": [ "-jar", "/path/to/youtube-mcp-server.jar" ], "env": { "YOUTUBE_API_KEY": "<your_api_key_here>" } } } }

    Building a Learning Assistant AI agent with MCP

    Now let us build a Learning Assistant AI agent which will use the Youtube MCP server we created above.

    Let us first initialize a new Spring Boot project with the following dependencies:

    • Spring Web
    • Model Context Protocol Client
    • OpenAI

    Configuring MCP Servers

    We will add the below configuration to application.yml file to enable MCP client and configure the MCP servers.

    spring: ai: mcp: client: toolcallback: enabled: true stdio: servers-configuration: classpath:mcp-servers.json sse: connections: youtube-mcp: url: http://localhost:8080

    Also we will add a mcp-servers.json file in src/main/resources folder to add all stdio based MCP servers here.

    { "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/Shared/tutorials" ] }, "brave-search": { "command": "docker", "args": ["run", "-i", "--rm", "-e", "BRAVE_API_KEY", "mcp/brave-search"], "env": { "BRAVE_API_KEY": "api key" } } } }

    Create a Learning Assistant Service

    Now let us add a LearningAssistantService which will plugin the MCP servers to OpenAI API.

    @Service public class LearningAssistantService { private final ChatClient chatClient; public LearningAssistantService(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory,List<McpSyncClient> mcpSyncClients) { this.chatClient = chatClientBuilder .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(), new SimpleLoggerAdvisor()) .defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClients)) .defaultSystem(""" You are a learning assistant. Provide advice based on the user's message. You can suggest resources, study techniques, or any other relevant information. Use the tools provided to get latest information. All your responses should be in markdown format. """) .build(); } public String advice(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } }

    In the above code first we are creating a ChatClient with the below configuration:

    • Default Advisors: We are using MessageChatMemoryAdvisor to keep the chat history and SimpleLoggerAdvisor to log the LLM call and response.
    • Default Tool Callbacks: We are using SyncMcpToolCallbackProvider to pass all MCP clients available. This will give access to all the tools available in the MCP servers.
    • Default System Message: We are setting the system prompt so that LLM behaves like a learning assistant.

    Then we are using the chatClient to send the user message and get the response.

    Add a Chat Controller

    Now let us add a controller to expose the Learning Assistant service as a REST API.

    @RestController @RequestMapping("/chat") public class ChatController { private final LearningAssistantService assistant; public ChatController(LearningAssistantService assistant) { this.assistant = assistant; } @GetMapping public String chat(String userMessage) { return assistant.advice(userMessage); } }

    Now we can run the application, and it will start the Learning Assistant service on port 8080.

    Testing the Learning Assistant service

    We can test the Learning Assistant service using any REST client like Postman, curl or httpie. But I have created a simple UI using NextJS which you can find here learning-assistant-ui.

    Below are some screenshots from my testing:

    alt text

    alt text

    Conclusion

    In this video, we explored the Model Context Protocol (MCP) and its connection lifecycle. We built an MCP server for YouTube search and transcript using Spring AI, and integrated it with VS Code and a Learning Assistant AI agent.

    You can find the code for the MCP server and the learning assistant with MCP client on my GitHub repository.

    Video Version

    You can find the video version of this blog below


    For more in-depth tutorials on Java, Spring, and modern software development practices, follow me for more content:

    🔗 Blog 🔗 YouTube 🔗 LinkedIn 🔗 Medium 🔗 Github

    Stay tuned for more content on the latest in AI and software engineering!

    Summarise

    Transform Your Learning

    Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.

    Instant video summaries
    Smart insights extraction
    Channel tracking