{"article":{"slug":"model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java","title":"Model Context Protocol with Spring AI, Building MCP Clients and Servers in Java","subtitle":null,"summary":"Ayush Shrivastava walks through building MCP clients and servers with Spring AI in Java: tool discovery, protocol basics, and wiring MCP into agentic Spring applications beyond a basic demo.","content_type":"tutorial","language":"en","canonical_url":"https://dev.to/ayshriv/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java-2146","author":{"name":"Ayush Shrivastava","url":"https://dev.to/ayshriv","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"DEV Community","url":"https://dev.to/","listing_slug":null,"listing":null},"topics":[{"name":"AI","slug":"ai","url":"https://listedarticles.com/topics/ai"},{"name":"AI Agents","slug":"ai-agents","url":"https://listedarticles.com/topics/ai-agents"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Developer Tools","slug":"developer-tools","url":"https://listedarticles.com/topics/developer-tools"},{"name":"LLMs","slug":"llms","url":"https://listedarticles.com/topics/llms"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2972,"reading_minutes":13,"published_at":"2026-09-19T00:00:00.000Z","added_at":"2026-09-30T18:16:01.019Z","updated_at":"2026-09-30T18:16:01.019Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":false},"profile_url":"https://listedarticles.com/articles/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java","markdown_url":"https://listedarticles.com/articles/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java.md","example":false,"citation":"Ayush Shrivastava, DEV Community. \"Model Context Protocol with Spring AI, Building MCP Clients and Servers in Java.\" 19 Sept 2026. https://dev.to/ayshriv/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java-2146 (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://dev.to/ayshriv/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java-2146"},"body_markdown":"# Model Context Protocol with Spring AI: Building MCP Clients and Servers in Java\n\nIn the previous article, we explored how to build AI agents with **Spring AI** using:\n\n\n```\nLLMs\n ↓\nRAG\n ↓\nTool Calling\n ↓\nMemory\n ↓\nAgent Workflows\n```\nTool calling gives an AI application the ability to interact with external capabilities.\n\nBut another problem appears as AI systems become larger.\n\nImagine you have:\n\n\n```\nCustomer Service Agent\n        ↓\nOrder APIs\nPayment APIs\nCRM APIs\nKnowledge Base\nEmail Service\n```\nAnd another application has:\n\n\n```\nSales Agent\n        ↓\nCRM\nCalendar\nEmail\nCustomer Database\n```\nAnd another has:\n\n\n```\nDeveloper Agent\n        ↓\nGit Repository\nIssue Tracker\nCI/CD\nDocumentation\n```\nIf every AI application implements every integration differently, the architecture quickly becomes difficult to maintain.\n\nThis is where **Model Context Protocol (MCP)** becomes interesting.\n\nMCP provides a standardized way for AI applications to interact with external tools and resources. Spring AI provides support for both building MCP servers and consuming MCP servers from Spring Boot applications.\n\nIn this article, we'll build a mental model for MCP and explore how Java developers can use it with Spring AI.\n\n# What Is MCP?\n\nMCP stands for:\n\n**Model Context Protocol**\n\n\nAt a high level, MCP standardizes how an AI application communicates with external capabilities such as:\n\n\n```\nTools\nResources\nPrompts\n```\nInstead of every AI application inventing its own integration mechanism:\n\n\n```\nAI Application\n ↓\nCustom Tool Integration\n ↓\nCRM\n```\nwe can have:\n\n\n```\nAI Application\n ↓\nMCP Client\n ↓\nMCP Protocol\n ↓\nMCP Server\n ↓\nCRM\n```\nThe MCP server exposes capabilities through a standardized interface.\n\nThe AI application doesn't need to understand every internal implementation detail of the external system.\n\n# Why MCP Exists\n\nSuppose you build an AI assistant that needs access to:\n\n\n```\nGitHub\nSlack\nPostgreSQL\nGoogle Calendar\nInternal APIs\nFile Systems\n```\nWithout a standard protocol, your application might contain:\n\n\n```\nGitHub Integration\nSlack Integration\nPostgreSQL Integration\nCalendar Integration\nInternal API Integration\n```\nEach integration may have its own:\n\n\n```\nAuthentication\nTool Schema\nRequest Format\nResponse Format\nConnection Management\nError Handling\n```\nNow imagine another AI application needs the same capabilities.\n\nYou may end up rebuilding many of the same integrations.\n\nMCP addresses this by creating a common protocol for AI applications and external servers.\n\nConceptually:\n\n\n```\n                    AI Application\n                         │\n                    MCP Client\n                         │\n                MCP Protocol\n                         │\n       ┌─────────────────┼─────────────────┐\n       ↓                 ↓                 ↓\n  MCP Server         MCP Server         MCP Server\n       ↓                 ↓                 ↓\n     CRM              GitHub            Database\n```\nThis is one of the main ideas behind MCP.\n\n# MCP Is Not an LLM\n\nThis distinction is important.\n\nMCP is not:\n\n\n```\nAn AI model\n```\nIt is a protocol for connecting AI applications with capabilities.\n\nThink of the stack like this:\n\n\n```\nLLM\n ↓\nAI Application\n ↓\nMCP Client\n ↓\nMCP Protocol\n ↓\nMCP Server\n ↓\nTools / Resources\n ↓\nExternal System\n```\nThe LLM performs reasoning.\n\nThe MCP layer provides standardized communication.\n\nThe external system performs the actual operation.\n\n# MCP Client vs MCP Server\n\nMCP introduces two important roles.\n\n## MCP Client\n\nThe MCP client lives inside the AI application.\n\nIts responsibility is to connect to MCP servers and interact with the capabilities they expose.\n\nFor example:\n\n\n```\nSpring Boot AI Application\n        ↓\nMCP Client\n        ↓\nWeather MCP Server\n```\nThe client can discover and use the server's available capabilities.\n\n## MCP Server\n\nThe MCP server exposes capabilities.\n\nFor example:\n\n\n```\nWeather MCP Server\nTools:\ngetWeather()\ngetForecast()\nResources:\nweather://cities\nPrompts:\nweather-analysis\n```\nThe server is responsible for implementing those capabilities.\n\nSpring AI provides Boot starters and APIs for both sides of this architecture.\n\n# The Basic MCP Architecture\n\nA simplified architecture looks like:\n\n\n```\n                    User\n                     ↓\n                 Spring Boot\n                     ↓\n                  ChatClient\n                     ↓\n                  MCP Client\n                     ↓\n                MCP Protocol\n                     ↓\n                MCP Server\n                     ↓\n                   Tool\n                     ↓\n                External API\n```\nFor example:\n\n\n```\nUser:\nWhat's the weather in Paris?\n```\nThe AI application can discover a weather tool exposed by an MCP server.\n\nThe flow becomes:\n\n\n```\nUser\n ↓\nLLM\n ↓\nMCP Tool\n ↓\nWeather MCP Server\n ↓\nWeather API\n ↓\nTool Result\n ↓\nLLM\n ↓\nFinal Answer\n```\n# MCP and Traditional Tool Calling\n\nAt this point, you might ask:\n\n\"Isn't this just tool calling?\"\n\n\nThere is an important distinction.\n\nTraditional Spring AI tool calling can expose application methods directly:\n\n\n```\n@Tool\npublic String getWeather(String city) {\n    return weatherService.getWeather(city);\n}\n```\nYour application owns the tool.\n\nWith MCP:\n\n\n```\nAI Application\n      ↓\nMCP Client\n      ↓\nRemote MCP Server\n      ↓\nTool\n```\nThe tool can live outside the application.\n\nThis creates a cleaner separation between:\n\n\n```\nAI Application\n```\nand:\n\n\n```\nCapability Provider\n```\nSpring AI integrates MCP tools into its tool-calling architecture, allowing applications to consume tools exposed by MCP servers.\n\n# MCP Tools\n\nOne of the most important MCP capabilities is the **tool**.\n\nA tool represents an action that an AI application can invoke.\n\nFor example:\n\n\n```\ngetWeather()\ncreateTicket()\nsearchCustomers()\ngetOrder()\nsendEmail()\n```\nA weather server might expose:\n\n\n```\ngetTemperature(city)\n```\nA CRM server might expose:\n\n\n```\nfindCustomer(email)\ncreateLead(customer)\nupdateLead(leadId)\n```\nA developer server might expose:\n\n\n```\nsearchRepository(query)\ngetBuildStatus()\ncreateIssue(title)\n```\nThe MCP client can discover these tools and make them available to the AI application.\n\n# MCP Resources\n\nMCP is not limited to actions.\n\nIt can also expose **resources**.\n\nA resource represents information that an MCP client can access.\n\nFor example:\n\n\n```\ncustomer://123\norder://ORD-10291\nfile://README.md\ndatabase://schema\n```\nThink of the distinction as:\n\n\n```\nTool\n=\nDo something\nResource\n=\nAccess something\n```\nFor example:\n\n\n```\nTool:\ncreateTicket()\nResource:\ncustomer://123\n```\nA server can expose both.\n\n# MCP Prompts\n\nMCP also supports prompts.\n\nA server can provide reusable prompt templates for specific tasks.\n\nFor example:\n\n\n```\nPrompt:\nanalyze-customer\nInput:\ncustomerId\n```\nOr:\n\n\n```\nPrompt:\nsummarize-order\nInput:\norderId\n```\nThis allows prompt templates to become part of the server-provided capabilities rather than being hardcoded independently in every client.\n\nSpring AI's MCP support includes annotations for tools, resources, and prompts.\n\n# Building an MCP Server with Spring AI\n\nLet's build a simple MCP server.\n\nImagine a weather service.\n\nOur application already has:\n\n\n```\n@Service\npublic class WeatherService {\n    public String getTemperature(String city) {\n        return \"22°C\";\n    }\n}\n```\nWe can expose this capability through an MCP tool.\n\nWith Spring AI's annotation-based MCP support:\n\n\n```\n@Service\npublic class WeatherTools {\n    @McpTool(description = \"Get the current temperature for a city\")\n    public String getTemperature(\n            @McpToolParam(\n                description = \"City name\",\n                required = true\n            )\n            String city) {\n        return weatherService.getTemperature(city);\n    }\n}\n```\nThe MCP annotation model allows Spring services to expose capabilities as MCP operations.\n\n# Creating the MCP Server\n\nFor a Spring Boot application, Spring AI provides MCP server starters.\n\nFor example:\n\n\n```\n<dependency>\n    <groupId>org.springframework.ai</groupId>\n    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>\n</dependency>\n```\nYou can configure the server to use Streamable HTTP:\n\n\n```\nspring.ai.mcp.server.protocol=STREAMABLE\n```\nSpring AI 2.x supports MCP server transports including Streamable HTTP, stateless Streamable HTTP, SSE, and STDIO. Streamable HTTP is the current recommended HTTP transport in Spring AI 2.x, while SSE is deprecated for this use case.\n\n# What Happens Inside the MCP Server?\n\nConceptually:\n\n\n```\nSpring Boot\n     ↓\nMCP Server\n     ↓\nTool Registry\n     ↓\n@McpTool\n     ↓\nWeatherService\n     ↓\nWeather API\n```\nThe server exposes the tool through the MCP protocol.\n\nThe client doesn't need to know how the weather service works internally.\n\nIt only needs to understand:\n\n\n```\nTool Name\nDescription\nInput Schema\n```\n# Building an MCP Client\n\nNow let's create the other side.\n\nSuppose our AI application needs to consume the weather MCP server.\n\nAdd the MCP client starter:\n\n\n```\n<dependency>\n    <groupId>org.springframework.ai</groupId>\n    <artifactId>spring-ai-starter-mcp-client</artifactId>\n</dependency>\n```\nThen configure the MCP server connection.\n\nFor example, using Streamable HTTP:\n\n\n```\nspring:\n  ai:\n    mcp:\n      client:\n        streamable-http:\n          connections:\n            weather-server:\n              url: http://localhost:8080\n```\nSpring AI can connect to the configured MCP server and discover its tools.\n\n# Connecting MCP Tools to ChatClient\n\nOnce the MCP client discovers the server's tools, those tools can be integrated into Spring AI's tool-calling architecture.\n\nConceptually:\n\n\n```\n@Bean\nCommandLineRunner demo(\n        ChatClient chatClient,\n        ToolCallbackProvider mcpTools) {\n    return args -> {\n        String response = chatClient\n                .prompt(\"What's the weather in Paris?\")\n                .tools(mcpTools)\n                .call()\n                .content();\n        System.out.println(response);\n    };\n}\n```\nThis is a powerful abstraction.\n\nThe application doesn't need to manually implement every weather function.\n\nThe MCP server provides the capability.\n\nThe MCP client discovers it.\n\nSpring AI makes the discovered tools available to the model.\n\nThe flow becomes:\n\n\n```\nUser\n ↓\nChatClient\n ↓\nLLM\n ↓\nMCP Tool\n ↓\nMCP Client\n ↓\nMCP Server\n ↓\nWeather API\n ↓\nTool Result\n ↓\nLLM\n ↓\nAnswer\n```\nSpring AI's current MCP documentation demonstrates this client pattern using `ToolCallbackProvider`.\n\n# MCP Tool Discovery\n\nOne of the interesting capabilities of MCP is tool discovery.\n\nInstead of hardcoding:\n\n\n```\nTool A\nTool B\nTool C\n```\nthe client can connect to an MCP server and discover what capabilities it provides.\n\nFor example:\n\n\n```\nMCP Server\n ↓\ntools/list\n ↓\ngetWeather()\ngetForecast()\nsearchAlerts()\n```\nThe AI application can then make these tools available to the model.\n\nThis creates a more modular architecture.\n\n# Multiple MCP Servers\n\nNow imagine our AI assistant needs multiple capabilities.\n\nWe could have:\n\n\n```\nAI Application\n      │\n      ├── MCP Client\n      │\n      ├── Weather Server\n      │\n      ├── CRM Server\n      │\n      ├── GitHub Server\n      │\n      └── Internal API Server\n```\nThe architecture becomes:\n\n\n```\n                         AI Agent\n                            │\n                        MCP Client\n                            │\n             ┌──────────────┼──────────────┐\n             ↓              ↓              ↓\n        Weather MCP      CRM MCP       GitHub MCP\n             ↓              ↓              ↓\n        Weather API       CRM API      GitHub API\n```\nThe AI application can consume tools from multiple MCP servers.\n\nThis is one reason MCP becomes useful as an AI system grows.\n\n# MCP + Spring AI Agents\n\nNow connect this to the previous article.\n\nWe previously had:\n\n\n```\nAgent\n ↓\nTools\n ↓\nAPIs\n```\nWith MCP, we can move the tools outside the application boundary:\n\n\n```\nAgent\n ↓\nMCP Client\n ↓\nMCP Servers\n ├── CRM\n ├── Payments\n ├── Search\n └── Internal APIs\n```\nThe resulting architecture becomes:\n\n\n```\n                         User\n                           ↓\n                       Spring Boot\n                           ↓\n                        ChatClient\n                           ↓\n                         Agent\n                           ↓\n                       MCP Client\n                           ↓\n             ┌─────────────┼─────────────┐\n             ↓             ↓             ↓\n          CRM MCP      Payment MCP    Search MCP\n             ↓             ↓             ↓\n           CRM API     Payment API   Search API\n```\nThis creates a modular tool ecosystem.\n\n# MCP vs Direct Tool Calling\n\nLet's compare the two approaches.\n\n## Direct Spring AI Tool\n\n```\nChatClient\n    ↓\n@Tool\n    ↓\nService\n    ↓\nDatabase\n```\nEverything lives inside the application.\n\n## MCP Tool\n\n```\nChatClient\n    ↓\nMCP Client\n    ↓\nMCP Server\n    ↓\nService\n    ↓\nDatabase\n```\nThe capability provider can be separated from the AI application.\n\nThis can be useful when:\n\n- Multiple AI applications need the same capability\n- Tools need independent deployment\n- Teams own different integrations\n- External systems need standardized AI access\n- You want a reusable tool ecosystem\n\n# MCP Server as a Capability Layer\n\nOne useful architectural pattern is:\n\n\n```\nBusiness System\n       ↓\nMCP Server\n       ↓\nAI Applications\n```\nFor example:\n\n\n```\nCRM\n ↓\nCRM MCP Server\n ↓\n ├── Sales Agent\n ├── Support Agent\n └── Internal Assistant\n```\nInstead of implementing CRM integration separately in every AI application, the MCP server becomes the standardized capability layer.\n\n# MCP + RAG\n\nMCP doesn't replace RAG.\n\nThey solve different problems.\n\nRAG:\n\n\n```\nRetrieve relevant knowledge\n```\nMCP:\n\n\n```\nConnect AI applications to external capabilities\n```\nYou can combine them:\n\n\n```\n                       Agent\n                         ↓\n             ┌───────────┼───────────┐\n             ↓           ↓           ↓\n            RAG        MCP Tools    Memory\n             ↓           ↓           ↓\n        Vector DB    External APIs  Database\n```\nFor example:\n\n\n```\nUser:\nCan I refund order ORD-10291?\n```\nThe agent could:\n\n\n```\n1. MCP → Get order information\n2. RAG → Retrieve refund policy\n3. Agent → Compare the two\n4. Return answer\n```\nThis gives the model both:\n\n\n```\nLive Data\n+\nBusiness Knowledge\n```\n# MCP + Memory\n\nMemory can also coexist with MCP.\n\nFor example:\n\n\n```\nUser:\nUse my preferred delivery address.\nAgent:\nWhich address?\nUser:\nThe one I used last time.\n```\nThe application may use:\n\n\n```\nMemory\n ↓\nPrevious Address\n```\nwhile MCP provides:\n\n\n```\nOrder Service\n ↓\nUpdate Delivery Address\n```\nThe complete flow becomes:\n\n\n```\nAgent\n ├── Memory\n ├── RAG\n └── MCP\n       ├── Orders\n       ├── Payments\n       └── CRM\n```\nThis is becoming a much more complete agent architecture.\n\n# MCP Transports\n\nMCP supports multiple ways for clients and servers to communicate.\n\nCommon options include:\n\n\n```\nSTDIO\nSSE\nStreamable HTTP\nStateless Streamable HTTP\n```\nFor local process-based integrations:\n\n\n```\nAI Application\n ↓\nSTDIO\n ↓\nMCP Server Process\n```\nFor network-based applications:\n\n\n```\nAI Application\n ↓\nHTTP\n ↓\nMCP Server\n```\nIn Spring AI 2.x, Streamable HTTP is the current HTTP-oriented approach, while SSE has been deprecated in favor of Streamable HTTP.\n\n# STDIO vs HTTP\n\nA simple way to think about it:\n\n### STDIO\n\n```\nApplication\n ↓\nLocal MCP Process\n```\nUseful for local integrations and process-based communication.\n\n### Streamable HTTP\n\n```\nApplication\n ↓\nNetwork\n ↓\nMCP Server\n```\nUseful when the MCP server runs as an independent service.\n\n### Stateless Streamable HTTP\n\n```\nClient\n ↓\nRequest\n ↓\nServer\n ↓\nResponse\n```\nThis can be useful for stateless, cloud-native service architectures.\n\nThe right transport depends on deployment and communication requirements.\n\n# MCP Security\n\nThis is extremely important.\n\nAn MCP server may expose powerful capabilities:\n\n\n```\nreadCustomer()\ncreateInvoice()\nrefundPayment()\ndeleteUser()\n```\nSimply exposing those tools does not make them safe.\n\nSpring AI's MCP server starters do not automatically provide authentication or authorization for network-accessible MCP endpoints. The documentation specifically warns that HTTP-based MCP endpoints need a security boundary before being exposed beyond localhost.\n\nA production architecture should look like:\n\n\n```\nClient\n ↓\nAuthentication\n ↓\nAuthorization\n ↓\nMCP Server\n ↓\nTool\n ↓\nBusiness Logic\n```\nNot:\n\n\n```\nInternet\n ↓\nMCP Server\n ↓\nDangerous Tool\n```\n# MCP Tool Authorization\n\nImagine an MCP server exposes:\n\n\n```\ngetCustomer()\nupdateCustomer()\ndeleteCustomer()\n```\nDifferent users should have different capabilities.\n\nFor example:\n\n\n```\nREAD\n ↓\ngetCustomer()\nWRITE\n ↓\nupdateCustomer()\nDESTRUCTIVE\n ↓\ndeleteCustomer()\n```\nYour security layer should determine whether the caller is allowed to invoke each capability.\n\nThe model should never be considered the authorization layer.\n\nThe application must enforce it.\n\n# MCP and Multi-Tenant Systems\n\nMCP becomes particularly interesting in SaaS environments.\n\nSuppose:\n\n\n```\nTenant A\n ↓\nCRM MCP Server\n```\nand:\n\n\n```\nTenant B\n ↓\nCRM MCP Server\n```\nThe MCP layer must preserve tenant context.\n\nA request might carry:\n\n\n```\ntenantId\nuserId\nroles\npermissions\n```\nThe server can then enforce:\n\n\n```\nAuthentication\n ↓\nTenant Resolution\n ↓\nAuthorization\n ↓\nTool Execution\n ↓\nTenant-Scoped Data\n```\nThis is especially important for tools such as:\n\n\n```\nsearchCustomers()\ngetInvoices()\nsearchDocuments()\ncreateTicket()\n```\nA model must never be able to use a tool to cross tenant boundaries.\n\n# MCP Error Handling\n\nExternal tools can fail.\n\nFor example:\n\n\n```\nAgent\n ↓\nMCP Tool\n ↓\nCRM API\n ↓\nTimeout\n```\nYour application needs controlled failure behavior.\n\nFor example:\n\n\n```\nTool Failure\n ↓\nCapture Error\n ↓\nReturn Structured Result\n ↓\nAgent\n ↓\nRetry / Alternative Tool / Final Response\n```\nThe agent might decide:\n\n\n```\nCRM unavailable.\nTry cached customer information.\n```\nOr:\n\n\n```\nUnable to retrieve the customer's order.\nPlease try again later.\n```\nThe important part is that failures should be observable and controlled.\n\n# MCP Observability\n\nWhen MCP is added to an agent architecture, your observability requirements increase.\n\nYou may need to track:\n\n\n```\nMCP Server\nMCP Client\nTool Name\nTool Arguments\nRequest ID\nLatency\nStatus\nErrors\nRetries\nModel Calls\nToken Usage\n```\nA useful trace could look like:\n\n\n```\nUser Request\n    ↓\nLLM Call\n    ↓\nMCP Tool Discovery\n    ↓\nTool Call\n    ↓\nCRM API\n    ↓\nTool Result\n    ↓\nLLM Call\n    ↓\nFinal Response\n```\nWithout tracing, debugging multi-server agent systems can become difficult.\n\n# MCP Doesn't Replace Your Business Logic\n\nThis is another important principle.\n\nSuppose you have:\n\n\n```\npublic RefundResult refundPayment(\n        String orderId,\n        BigDecimal amount) {\n    ...\n}\n```\nYou shouldn't move all business logic into an MCP handler.\n\nInstead:\n\n\n```\nMCP Tool\n ↓\nApplication Service\n ↓\nBusiness Rules\n ↓\nRepository\n ↓\nDatabase\n```\nFor example:\n\n\n```\n@McpTool(description = \"Refund an eligible order\")\npublic RefundResult refundOrder(String orderId) {\n    return refundService.refund(orderId);\n}\n```\nThe MCP layer becomes an interface.\n\nYour existing business service remains responsible for the actual business rules.\n\nThis keeps the architecture clean.\n\n# MCP as an Integration Boundary\n\nOne of the strongest ways to think about MCP is as an integration boundary.\n\nInstead of:\n\n\n```\nAI\n ↓\nEverything\n```\nuse:\n\n\n```\nAI\n ↓\nMCP\n ↓\nControlled Capabilities\n```\nThe MCP layer becomes a contract between AI applications and external systems.\n\nFor example:\n\n\n```\nAI Application\n      ↓\nMCP\n      ↓\nCRM\n```\nor:\n\n\n```\nAI Application\n      ↓\nMCP\n      ↓\nPayment System\n```\nor:\n\n\n```\nAI Application\n      ↓\nMCP\n      ↓\nInternal Developer Platform\n```\n# A Complete Spring AI + MCP Architecture\n\nNow combine everything from this series:\n\n\n```\n                              User\n                                ↓\n                         Spring Boot API\n                                ↓\n                           ChatClient\n                                ↓\n                              Agent\n                                ↓\n             ┌──────────────────┼──────────────────┐\n             ↓                  ↓                  ↓\n           Memory              RAG              MCP Client\n             ↓                  ↓                  ↓\n         PostgreSQL          pgvector        ┌─────┼─────┐\n                                             ↓     ↓     ↓\n                                           CRM  GitHub  Search\n                                           MCP    MCP     MCP\n                                             ↓     ↓     ↓\n                                           APIs  APIs   APIs\n```\nAround the system:\n\n\n```\nAuthentication\nAuthorization\nTenant Isolation\nObservability\nAudit Logging\nRate Limiting\nGuardrails\nHuman Approval\n```\nThis is a strong foundation for production-oriented AI applications.\n\n# When Should You Use MCP?\n\nMCP becomes particularly useful when you have:\n\n\n```\nMultiple AI applications\n        ↓\nShared tools\n        ↓\nShared integrations\n```\nFor example:\n\n\n```\nSales Agent\nSupport Agent\nDeveloper Agent\nInternal Assistant\n```\nall need access to:\n\n\n```\nCRM\nGitHub\nInternal APIs\nDocumentation\n```\nInstead of implementing each integration separately:\n\n\n```\nAgent A → CRM Integration\nAgent B → CRM Integration\nAgent C → CRM Integration\n```\nyou can create:\n\n\n```\nCRM MCP Server\n```\nand allow multiple AI applications to consume it.\n\n# When You Don't Need MCP\n\nMCP isn't automatically required for every AI application.\n\nIf your application has:\n\n\n```\nOne Agent\n ↓\nOne Tool\n ↓\nOne Internal Service\n```\ndirect Spring AI tool calling may be simpler.\n\nFor example:\n\n\n```\nChatClient\n ↓\n@Tool\n ↓\nOrderService\n```\nIntroducing an MCP server could add unnecessary infrastructure.\n\nA useful rule is:\n\nUse MCP when standardization, reuse, separation, or interoperability provides real value.\n\n\nDon't introduce another protocol simply because it is popular.\n\n# Direct Tools vs MCP\n\nA simple comparison:\n\n| Approach | Best suited for | \n|---|---|\n| Spring AI `@Tool` | Local application capabilities | \n| MCP | Shared/external capabilities | \n| RAG | Knowledge retrieval | \n| Memory | Conversation context | \n| Agent | Dynamic decision-making | \n\nThey are not mutually exclusive.\n\nA production system may use all of them:\n\n\n```\nAgent\n ├── Local Spring AI Tools\n ├── MCP Tools\n ├── RAG\n └── Memory\n```\n# The Bigger Picture\n\nOur AI architecture has evolved throughout this series.\n\nWe started with:\n\n\n```\nLLM\n ↓\nResponse\n```\nThen:\n\n\n```\nLLM\n ↓\nRAG\n ↓\nKnowledge\n```\nThen:\n\n\n```\nLLM\n ↓\nTools\n ↓\nActions\n```\nThen:\n\n\n```\nLLM\n ↓\nTools\n ↓\nMemory\n ↓\nAgent\n```\nAnd now:\n\n\n```\nAgent\n ↓\nMCP\n ↓\nExternal Capabilities\n```\nThe architecture is becoming increasingly modular.\n\n# A Mental Model for MCP\n\nRemember it this way:\n\n\n```\nLLM\n=\nReason\nRAG\n=\nRetrieve Knowledge\nMemory\n=\nRemember Context\nTool Calling\n=\nInvoke Capabilities\nMCP\n=\nStandardize Capability Access\nSpring Boot\n=\nBusiness Application\nAgent\n=\nCoordinate Decisions\n```\nTogether:\n\n\n```\nLLM\n+\nRAG\n+\nMemory\n+\nTools\n+\nMCP\n+\nBusiness Logic\n=\nProduction AI Application\n```\n# Final Takeaways\n\nMCP gives AI applications a standardized way to interact with external tools and resources.\n\nThe key ideas are:\n\n- \n**MCP Client** connects an AI application to MCP servers.\n- \n**MCP Server** exposes tools, resources, and prompts.\n- \n**Tools** allow actions to be performed.\n- \n**Resources** provide access to information.\n- \n**Prompts** can provide reusable prompt templates.\n- Spring AI supports MCP clients and servers through Boot starters and annotations.\n- MCP tools integrate with Spring AI's existing tool-calling architecture.\n- Streamable HTTP is the current HTTP-oriented transport in Spring AI 2.x.\n- MCP does not replace your business logic.\n- Authentication and authorization must be enforced before exposing network-accessible MCP servers.\n- Multi-tenant applications must preserve tenant isolation across MCP calls.\n- MCP is particularly useful when capabilities need to be shared across multiple AI applications.\n- For simple local integrations, direct Spring AI tools may be sufficient.\n\nThe architecture can now look like:\n\n\n```\n                         User\n                           ↓\n                        Agent\n                           ↓\n        ┌──────────────────┼──────────────────┐\n        ↓                  ↓                  ↓\n      Memory              RAG             MCP Client\n        ↓                  ↓                  ↓\n    Conversation       Vector DB       MCP Servers\n                                             ↓\n                              ┌──────────────┼──────────────┐\n                              ↓              ↓              ↓\n                             CRM          GitHub         Internal APIs\n```\nThe important shift is this:\n\n\n```\nBefore:\nAI Application\n ↓\nCustom Integrations\n ↓\nExternal Systems\n```\nWith MCP:\n\n\n```\nAI Application\n ↓\nMCP Client\n ↓\nStandardized Protocol\n ↓\nMCP Servers\n ↓\nExternal Capabilities\n```\nMCP doesn't make your AI application automatically intelligent.\n\nIt gives your AI application a **standardized way to connect to capabilities**.\n\nAnd when you combine MCP with Spring AI's:\n\n\n```\nChatClient\n+\nTool Calling\n+\nRAG\n+\nMemory\n+\nAgents\n```\nyou get a powerful foundation for building modular AI applications in Java.\n\n## What's Next?\n\nWe've now connected our AI agent to external capabilities.\n\nBut another challenge appears:\n\n\n```\nOne Agent\n      ↓\nMultiple MCP Servers\n      ↓\nMultiple Tools\n      ↓\nMultiple Decisions\n```\nHow do we control which tools an agent can access?\n\nHow do we handle permissions?\n\nHow do we observe agent behavior?\n\nHow do we evaluate whether an agent is making the right decisions?\n\nAnd how do we build reliable AI workflows instead of simply hoping the model does the right thing?\n\nThat takes us into the next stage of AI engineering:\n\n**Building Production-Ready AI Agents with Spring AI — Guardrails, Evaluation, Observability, and Human-in-the-Loop Workflows.**","body_html":"<h1 id=\"model-context-protocol-with-spring-ai-building-mcp-clients-and-s\">Model Context Protocol with Spring AI: Building MCP Clients and Servers in Java</h1>\n<p>In the previous article, we explored how to build AI agents with <strong>Spring AI</strong> using:</p>\n<pre><code>LLMs\n ↓\nRAG\n ↓\nTool Calling\n ↓\nMemory\n ↓\nAgent Workflows</code></pre>\n<p>Tool calling gives an AI application the ability to interact with external capabilities.</p>\n<p>But another problem appears as AI systems become larger.</p>\n<p>Imagine you have:</p>\n<pre><code>Customer Service Agent\n        ↓\nOrder APIs\nPayment APIs\nCRM APIs\nKnowledge Base\nEmail Service</code></pre>\n<p>And another application has:</p>\n<pre><code>Sales Agent\n        ↓\nCRM\nCalendar\nEmail\nCustomer Database</code></pre>\n<p>And another has:</p>\n<pre><code>Developer Agent\n        ↓\nGit Repository\nIssue Tracker\nCI/CD\nDocumentation</code></pre>\n<p>If every AI application implements every integration differently, the architecture quickly becomes difficult to maintain.</p>\n<p>This is where <strong>Model Context Protocol (MCP)</strong> becomes interesting.</p>\n<p>MCP provides a standardized way for AI applications to interact with external tools and resources. Spring AI provides support for both building MCP servers and consuming MCP servers from Spring Boot applications.</p>\n<p>In this article, we&#39;ll build a mental model for MCP and explore how Java developers can use it with Spring AI.</p>\n<h1 id=\"what-is-mcp\">What Is MCP?</h1>\n<p>MCP stands for:</p>\n<p><strong>Model Context Protocol</strong></p>\n<p>At a high level, MCP standardizes how an AI application communicates with external capabilities such as:</p>\n<pre><code>Tools\nResources\nPrompts</code></pre>\n<p>Instead of every AI application inventing its own integration mechanism:</p>\n<pre><code>AI Application\n ↓\nCustom Tool Integration\n ↓\nCRM</code></pre>\n<p>we can have:</p>\n<pre><code>AI Application\n ↓\nMCP Client\n ↓\nMCP Protocol\n ↓\nMCP Server\n ↓\nCRM</code></pre>\n<p>The MCP server exposes capabilities through a standardized interface.</p>\n<p>The AI application doesn&#39;t need to understand every internal implementation detail of the external system.</p>\n<h1 id=\"why-mcp-exists\">Why MCP Exists</h1>\n<p>Suppose you build an AI assistant that needs access to:</p>\n<pre><code>GitHub\nSlack\nPostgreSQL\nGoogle Calendar\nInternal APIs\nFile Systems</code></pre>\n<p>Without a standard protocol, your application might contain:</p>\n<pre><code>GitHub Integration\nSlack Integration\nPostgreSQL Integration\nCalendar Integration\nInternal API Integration</code></pre>\n<p>Each integration may have its own:</p>\n<pre><code>Authentication\nTool Schema\nRequest Format\nResponse Format\nConnection Management\nError Handling</code></pre>\n<p>Now imagine another AI application needs the same capabilities.</p>\n<p>You may end up rebuilding many of the same integrations.</p>\n<p>MCP addresses this by creating a common protocol for AI applications and external servers.</p>\n<p>Conceptually:</p>\n<pre><code>                    AI Application\n                         │\n                    MCP Client\n                         │\n                MCP Protocol\n                         │\n       ┌─────────────────┼─────────────────┐\n       ↓                 ↓                 ↓\n  MCP Server         MCP Server         MCP Server\n       ↓                 ↓                 ↓\n     CRM              GitHub            Database</code></pre>\n<p>This is one of the main ideas behind MCP.</p>\n<h1 id=\"mcp-is-not-an-llm\">MCP Is Not an LLM</h1>\n<p>This distinction is important.</p>\n<p>MCP is not:</p>\n<pre><code>An AI model</code></pre>\n<p>It is a protocol for connecting AI applications with capabilities.</p>\n<p>Think of the stack like this:</p>\n<pre><code>LLM\n ↓\nAI Application\n ↓\nMCP Client\n ↓\nMCP Protocol\n ↓\nMCP Server\n ↓\nTools / Resources\n ↓\nExternal System</code></pre>\n<p>The LLM performs reasoning.</p>\n<p>The MCP layer provides standardized communication.</p>\n<p>The external system performs the actual operation.</p>\n<h1 id=\"mcp-client-vs-mcp-server\">MCP Client vs MCP Server</h1>\n<p>MCP introduces two important roles.</p>\n<h2 id=\"mcp-client\">MCP Client</h2>\n<p>The MCP client lives inside the AI application.</p>\n<p>Its responsibility is to connect to MCP servers and interact with the capabilities they expose.</p>\n<p>For example:</p>\n<pre><code>Spring Boot AI Application\n        ↓\nMCP Client\n        ↓\nWeather MCP Server</code></pre>\n<p>The client can discover and use the server&#39;s available capabilities.</p>\n<h2 id=\"mcp-server\">MCP Server</h2>\n<p>The MCP server exposes capabilities.</p>\n<p>For example:</p>\n<pre><code>Weather MCP Server\nTools:\ngetWeather()\ngetForecast()\nResources:\nweather://cities\nPrompts:\nweather-analysis</code></pre>\n<p>The server is responsible for implementing those capabilities.</p>\n<p>Spring AI provides Boot starters and APIs for both sides of this architecture.</p>\n<h1 id=\"the-basic-mcp-architecture\">The Basic MCP Architecture</h1>\n<p>A simplified architecture looks like:</p>\n<pre><code>                    User\n                     ↓\n                 Spring Boot\n                     ↓\n                  ChatClient\n                     ↓\n                  MCP Client\n                     ↓\n                MCP Protocol\n                     ↓\n                MCP Server\n                     ↓\n                   Tool\n                     ↓\n                External API</code></pre>\n<p>For example:</p>\n<pre><code>User:\nWhat&#39;s the weather in Paris?</code></pre>\n<p>The AI application can discover a weather tool exposed by an MCP server.</p>\n<p>The flow becomes:</p>\n<pre><code>User\n ↓\nLLM\n ↓\nMCP Tool\n ↓\nWeather MCP Server\n ↓\nWeather API\n ↓\nTool Result\n ↓\nLLM\n ↓\nFinal Answer</code></pre>\n<h1 id=\"mcp-and-traditional-tool-calling\">MCP and Traditional Tool Calling</h1>\n<p>At this point, you might ask:</p>\n<p>&quot;Isn&#39;t this just tool calling?&quot;</p>\n<p>There is an important distinction.</p>\n<p>Traditional Spring AI tool calling can expose application methods directly:</p>\n<pre><code>@Tool\npublic String getWeather(String city) {\n    return weatherService.getWeather(city);\n}</code></pre>\n<p>Your application owns the tool.</p>\n<p>With MCP:</p>\n<pre><code>AI Application\n      ↓\nMCP Client\n      ↓\nRemote MCP Server\n      ↓\nTool</code></pre>\n<p>The tool can live outside the application.</p>\n<p>This creates a cleaner separation between:</p>\n<pre><code>AI Application</code></pre>\n<p>and:</p>\n<pre><code>Capability Provider</code></pre>\n<p>Spring AI integrates MCP tools into its tool-calling architecture, allowing applications to consume tools exposed by MCP servers.</p>\n<h1 id=\"mcp-tools\">MCP Tools</h1>\n<p>One of the most important MCP capabilities is the <strong>tool</strong>.</p>\n<p>A tool represents an action that an AI application can invoke.</p>\n<p>For example:</p>\n<pre><code>getWeather()\ncreateTicket()\nsearchCustomers()\ngetOrder()\nsendEmail()</code></pre>\n<p>A weather server might expose:</p>\n<pre><code>getTemperature(city)</code></pre>\n<p>A CRM server might expose:</p>\n<pre><code>findCustomer(email)\ncreateLead(customer)\nupdateLead(leadId)</code></pre>\n<p>A developer server might expose:</p>\n<pre><code>searchRepository(query)\ngetBuildStatus()\ncreateIssue(title)</code></pre>\n<p>The MCP client can discover these tools and make them available to the AI application.</p>\n<h1 id=\"mcp-resources\">MCP Resources</h1>\n<p>MCP is not limited to actions.</p>\n<p>It can also expose <strong>resources</strong>.</p>\n<p>A resource represents information that an MCP client can access.</p>\n<p>For example:</p>\n<pre><code>customer://123\norder://ORD-10291\nfile://README.md\ndatabase://schema</code></pre>\n<p>Think of the distinction as:</p>\n<pre><code>Tool\n=\nDo something\nResource\n=\nAccess something</code></pre>\n<p>For example:</p>\n<pre><code>Tool:\ncreateTicket()\nResource:\ncustomer://123</code></pre>\n<p>A server can expose both.</p>\n<h1 id=\"mcp-prompts\">MCP Prompts</h1>\n<p>MCP also supports prompts.</p>\n<p>A server can provide reusable prompt templates for specific tasks.</p>\n<p>For example:</p>\n<pre><code>Prompt:\nanalyze-customer\nInput:\ncustomerId</code></pre>\n<p>Or:</p>\n<pre><code>Prompt:\nsummarize-order\nInput:\norderId</code></pre>\n<p>This allows prompt templates to become part of the server-provided capabilities rather than being hardcoded independently in every client.</p>\n<p>Spring AI&#39;s MCP support includes annotations for tools, resources, and prompts.</p>\n<h1 id=\"building-an-mcp-server-with-spring-ai\">Building an MCP Server with Spring AI</h1>\n<p>Let&#39;s build a simple MCP server.</p>\n<p>Imagine a weather service.</p>\n<p>Our application already has:</p>\n<pre><code>@Service\npublic class WeatherService {\n    public String getTemperature(String city) {\n        return &quot;22°C&quot;;\n    }\n}</code></pre>\n<p>We can expose this capability through an MCP tool.</p>\n<p>With Spring AI&#39;s annotation-based MCP support:</p>\n<pre><code>@Service\npublic class WeatherTools {\n    @McpTool(description = &quot;Get the current temperature for a city&quot;)\n    public String getTemperature(\n            @McpToolParam(\n                description = &quot;City name&quot;,\n                required = true\n            )\n            String city) {\n        return weatherService.getTemperature(city);\n    }\n}</code></pre>\n<p>The MCP annotation model allows Spring services to expose capabilities as MCP operations.</p>\n<h1 id=\"creating-the-mcp-server\">Creating the MCP Server</h1>\n<p>For a Spring Boot application, Spring AI provides MCP server starters.</p>\n<p>For example:</p>\n<pre><code>&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;\n    &lt;artifactId&gt;spring-ai-starter-mcp-server-webmvc&lt;/artifactId&gt;\n&lt;/dependency&gt;</code></pre>\n<p>You can configure the server to use Streamable HTTP:</p>\n<pre><code>spring.ai.mcp.server.protocol=STREAMABLE</code></pre>\n<p>Spring AI 2.x supports MCP server transports including Streamable HTTP, stateless Streamable HTTP, SSE, and STDIO. Streamable HTTP is the current recommended HTTP transport in Spring AI 2.x, while SSE is deprecated for this use case.</p>\n<h1 id=\"what-happens-inside-the-mcp-server\">What Happens Inside the MCP Server?</h1>\n<p>Conceptually:</p>\n<pre><code>Spring Boot\n     ↓\nMCP Server\n     ↓\nTool Registry\n     ↓\n@McpTool\n     ↓\nWeatherService\n     ↓\nWeather API</code></pre>\n<p>The server exposes the tool through the MCP protocol.</p>\n<p>The client doesn&#39;t need to know how the weather service works internally.</p>\n<p>It only needs to understand:</p>\n<pre><code>Tool Name\nDescription\nInput Schema</code></pre>\n<h1 id=\"building-an-mcp-client\">Building an MCP Client</h1>\n<p>Now let&#39;s create the other side.</p>\n<p>Suppose our AI application needs to consume the weather MCP server.</p>\n<p>Add the MCP client starter:</p>\n<pre><code>&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;\n    &lt;artifactId&gt;spring-ai-starter-mcp-client&lt;/artifactId&gt;\n&lt;/dependency&gt;</code></pre>\n<p>Then configure the MCP server connection.</p>\n<p>For example, using Streamable HTTP:</p>\n<pre><code>spring:\n  ai:\n    mcp:\n      client:\n        streamable-http:\n          connections:\n            weather-server:\n              url: http://localhost:8080</code></pre>\n<p>Spring AI can connect to the configured MCP server and discover its tools.</p>\n<h1 id=\"connecting-mcp-tools-to-chatclient\">Connecting MCP Tools to ChatClient</h1>\n<p>Once the MCP client discovers the server&#39;s tools, those tools can be integrated into Spring AI&#39;s tool-calling architecture.</p>\n<p>Conceptually:</p>\n<pre><code>@Bean\nCommandLineRunner demo(\n        ChatClient chatClient,\n        ToolCallbackProvider mcpTools) {\n    return args -&gt; {\n        String response = chatClient\n                .prompt(&quot;What&#39;s the weather in Paris?&quot;)\n                .tools(mcpTools)\n                .call()\n                .content();\n        System.out.println(response);\n    };\n}</code></pre>\n<p>This is a powerful abstraction.</p>\n<p>The application doesn&#39;t need to manually implement every weather function.</p>\n<p>The MCP server provides the capability.</p>\n<p>The MCP client discovers it.</p>\n<p>Spring AI makes the discovered tools available to the model.</p>\n<p>The flow becomes:</p>\n<pre><code>User\n ↓\nChatClient\n ↓\nLLM\n ↓\nMCP Tool\n ↓\nMCP Client\n ↓\nMCP Server\n ↓\nWeather API\n ↓\nTool Result\n ↓\nLLM\n ↓\nAnswer</code></pre>\n<p>Spring AI&#39;s current MCP documentation demonstrates this client pattern using <code>ToolCallbackProvider</code>.</p>\n<h1 id=\"mcp-tool-discovery\">MCP Tool Discovery</h1>\n<p>One of the interesting capabilities of MCP is tool discovery.</p>\n<p>Instead of hardcoding:</p>\n<pre><code>Tool A\nTool B\nTool C</code></pre>\n<p>the client can connect to an MCP server and discover what capabilities it provides.</p>\n<p>For example:</p>\n<pre><code>MCP Server\n ↓\ntools/list\n ↓\ngetWeather()\ngetForecast()\nsearchAlerts()</code></pre>\n<p>The AI application can then make these tools available to the model.</p>\n<p>This creates a more modular architecture.</p>\n<h1 id=\"multiple-mcp-servers\">Multiple MCP Servers</h1>\n<p>Now imagine our AI assistant needs multiple capabilities.</p>\n<p>We could have:</p>\n<pre><code>AI Application\n      │\n      ├── MCP Client\n      │\n      ├── Weather Server\n      │\n      ├── CRM Server\n      │\n      ├── GitHub Server\n      │\n      └── Internal API Server</code></pre>\n<p>The architecture becomes:</p>\n<pre><code>                         AI Agent\n                            │\n                        MCP Client\n                            │\n             ┌──────────────┼──────────────┐\n             ↓              ↓              ↓\n        Weather MCP      CRM MCP       GitHub MCP\n             ↓              ↓              ↓\n        Weather API       CRM API      GitHub API</code></pre>\n<p>The AI application can consume tools from multiple MCP servers.</p>\n<p>This is one reason MCP becomes useful as an AI system grows.</p>\n<h1 id=\"mcp-spring-ai-agents\">MCP + Spring AI Agents</h1>\n<p>Now connect this to the previous article.</p>\n<p>We previously had:</p>\n<pre><code>Agent\n ↓\nTools\n ↓\nAPIs</code></pre>\n<p>With MCP, we can move the tools outside the application boundary:</p>\n<pre><code>Agent\n ↓\nMCP Client\n ↓\nMCP Servers\n ├── CRM\n ├── Payments\n ├── Search\n └── Internal APIs</code></pre>\n<p>The resulting architecture becomes:</p>\n<pre><code>                         User\n                           ↓\n                       Spring Boot\n                           ↓\n                        ChatClient\n                           ↓\n                         Agent\n                           ↓\n                       MCP Client\n                           ↓\n             ┌─────────────┼─────────────┐\n             ↓             ↓             ↓\n          CRM MCP      Payment MCP    Search MCP\n             ↓             ↓             ↓\n           CRM API     Payment API   Search API</code></pre>\n<p>This creates a modular tool ecosystem.</p>\n<h1 id=\"mcp-vs-direct-tool-calling\">MCP vs Direct Tool Calling</h1>\n<p>Let&#39;s compare the two approaches.</p>\n<h2 id=\"direct-spring-ai-tool\">Direct Spring AI Tool</h2>\n<pre><code>ChatClient\n    ↓\n@Tool\n    ↓\nService\n    ↓\nDatabase</code></pre>\n<p>Everything lives inside the application.</p>\n<h2 id=\"mcp-tool\">MCP Tool</h2>\n<pre><code>ChatClient\n    ↓\nMCP Client\n    ↓\nMCP Server\n    ↓\nService\n    ↓\nDatabase</code></pre>\n<p>The capability provider can be separated from the AI application.</p>\n<p>This can be useful when:</p>\n<ul><li>Multiple AI applications need the same capability</li><li>Tools need independent deployment</li><li>Teams own different integrations</li><li>External systems need standardized AI access</li><li>You want a reusable tool ecosystem</li></ul>\n<h1 id=\"mcp-server-as-a-capability-layer\">MCP Server as a Capability Layer</h1>\n<p>One useful architectural pattern is:</p>\n<pre><code>Business System\n       ↓\nMCP Server\n       ↓\nAI Applications</code></pre>\n<p>For example:</p>\n<pre><code>CRM\n ↓\nCRM MCP Server\n ↓\n ├── Sales Agent\n ├── Support Agent\n └── Internal Assistant</code></pre>\n<p>Instead of implementing CRM integration separately in every AI application, the MCP server becomes the standardized capability layer.</p>\n<h1 id=\"mcp-rag\">MCP + RAG</h1>\n<p>MCP doesn&#39;t replace RAG.</p>\n<p>They solve different problems.</p>\n<p>RAG:</p>\n<pre><code>Retrieve relevant knowledge</code></pre>\n<p>MCP:</p>\n<pre><code>Connect AI applications to external capabilities</code></pre>\n<p>You can combine them:</p>\n<pre><code>                       Agent\n                         ↓\n             ┌───────────┼───────────┐\n             ↓           ↓           ↓\n            RAG        MCP Tools    Memory\n             ↓           ↓           ↓\n        Vector DB    External APIs  Database</code></pre>\n<p>For example:</p>\n<pre><code>User:\nCan I refund order ORD-10291?</code></pre>\n<p>The agent could:</p>\n<pre><code>1. MCP → Get order information\n2. RAG → Retrieve refund policy\n3. Agent → Compare the two\n4. Return answer</code></pre>\n<p>This gives the model both:</p>\n<pre><code>Live Data\n+\nBusiness Knowledge</code></pre>\n<h1 id=\"mcp-memory\">MCP + Memory</h1>\n<p>Memory can also coexist with MCP.</p>\n<p>For example:</p>\n<pre><code>User:\nUse my preferred delivery address.\nAgent:\nWhich address?\nUser:\nThe one I used last time.</code></pre>\n<p>The application may use:</p>\n<pre><code>Memory\n ↓\nPrevious Address</code></pre>\n<p>while MCP provides:</p>\n<pre><code>Order Service\n ↓\nUpdate Delivery Address</code></pre>\n<p>The complete flow becomes:</p>\n<pre><code>Agent\n ├── Memory\n ├── RAG\n └── MCP\n       ├── Orders\n       ├── Payments\n       └── CRM</code></pre>\n<p>This is becoming a much more complete agent architecture.</p>\n<h1 id=\"mcp-transports\">MCP Transports</h1>\n<p>MCP supports multiple ways for clients and servers to communicate.</p>\n<p>Common options include:</p>\n<pre><code>STDIO\nSSE\nStreamable HTTP\nStateless Streamable HTTP</code></pre>\n<p>For local process-based integrations:</p>\n<pre><code>AI Application\n ↓\nSTDIO\n ↓\nMCP Server Process</code></pre>\n<p>For network-based applications:</p>\n<pre><code>AI Application\n ↓\nHTTP\n ↓\nMCP Server</code></pre>\n<p>In Spring AI 2.x, Streamable HTTP is the current HTTP-oriented approach, while SSE has been deprecated in favor of Streamable HTTP.</p>\n<h1 id=\"stdio-vs-http\">STDIO vs HTTP</h1>\n<p>A simple way to think about it:</p>\n<h3 id=\"stdio\">STDIO</h3>\n<pre><code>Application\n ↓\nLocal MCP Process</code></pre>\n<p>Useful for local integrations and process-based communication.</p>\n<h3 id=\"streamable-http\">Streamable HTTP</h3>\n<pre><code>Application\n ↓\nNetwork\n ↓\nMCP Server</code></pre>\n<p>Useful when the MCP server runs as an independent service.</p>\n<h3 id=\"stateless-streamable-http\">Stateless Streamable HTTP</h3>\n<pre><code>Client\n ↓\nRequest\n ↓\nServer\n ↓\nResponse</code></pre>\n<p>This can be useful for stateless, cloud-native service architectures.</p>\n<p>The right transport depends on deployment and communication requirements.</p>\n<h1 id=\"mcp-security\">MCP Security</h1>\n<p>This is extremely important.</p>\n<p>An MCP server may expose powerful capabilities:</p>\n<pre><code>readCustomer()\ncreateInvoice()\nrefundPayment()\ndeleteUser()</code></pre>\n<p>Simply exposing those tools does not make them safe.</p>\n<p>Spring AI&#39;s MCP server starters do not automatically provide authentication or authorization for network-accessible MCP endpoints. The documentation specifically warns that HTTP-based MCP endpoints need a security boundary before being exposed beyond localhost.</p>\n<p>A production architecture should look like:</p>\n<pre><code>Client\n ↓\nAuthentication\n ↓\nAuthorization\n ↓\nMCP Server\n ↓\nTool\n ↓\nBusiness Logic</code></pre>\n<p>Not:</p>\n<pre><code>Internet\n ↓\nMCP Server\n ↓\nDangerous Tool</code></pre>\n<h1 id=\"mcp-tool-authorization\">MCP Tool Authorization</h1>\n<p>Imagine an MCP server exposes:</p>\n<pre><code>getCustomer()\nupdateCustomer()\ndeleteCustomer()</code></pre>\n<p>Different users should have different capabilities.</p>\n<p>For example:</p>\n<pre><code>READ\n ↓\ngetCustomer()\nWRITE\n ↓\nupdateCustomer()\nDESTRUCTIVE\n ↓\ndeleteCustomer()</code></pre>\n<p>Your security layer should determine whether the caller is allowed to invoke each capability.</p>\n<p>The model should never be considered the authorization layer.</p>\n<p>The application must enforce it.</p>\n<h1 id=\"mcp-and-multi-tenant-systems\">MCP and Multi-Tenant Systems</h1>\n<p>MCP becomes particularly interesting in SaaS environments.</p>\n<p>Suppose:</p>\n<pre><code>Tenant A\n ↓\nCRM MCP Server</code></pre>\n<p>and:</p>\n<pre><code>Tenant B\n ↓\nCRM MCP Server</code></pre>\n<p>The MCP layer must preserve tenant context.</p>\n<p>A request might carry:</p>\n<pre><code>tenantId\nuserId\nroles\npermissions</code></pre>\n<p>The server can then enforce:</p>\n<pre><code>Authentication\n ↓\nTenant Resolution\n ↓\nAuthorization\n ↓\nTool Execution\n ↓\nTenant-Scoped Data</code></pre>\n<p>This is especially important for tools such as:</p>\n<pre><code>searchCustomers()\ngetInvoices()\nsearchDocuments()\ncreateTicket()</code></pre>\n<p>A model must never be able to use a tool to cross tenant boundaries.</p>\n<h1 id=\"mcp-error-handling\">MCP Error Handling</h1>\n<p>External tools can fail.</p>\n<p>For example:</p>\n<pre><code>Agent\n ↓\nMCP Tool\n ↓\nCRM API\n ↓\nTimeout</code></pre>\n<p>Your application needs controlled failure behavior.</p>\n<p>For example:</p>\n<pre><code>Tool Failure\n ↓\nCapture Error\n ↓\nReturn Structured Result\n ↓\nAgent\n ↓\nRetry / Alternative Tool / Final Response</code></pre>\n<p>The agent might decide:</p>\n<pre><code>CRM unavailable.\nTry cached customer information.</code></pre>\n<p>Or:</p>\n<pre><code>Unable to retrieve the customer&#39;s order.\nPlease try again later.</code></pre>\n<p>The important part is that failures should be observable and controlled.</p>\n<h1 id=\"mcp-observability\">MCP Observability</h1>\n<p>When MCP is added to an agent architecture, your observability requirements increase.</p>\n<p>You may need to track:</p>\n<pre><code>MCP Server\nMCP Client\nTool Name\nTool Arguments\nRequest ID\nLatency\nStatus\nErrors\nRetries\nModel Calls\nToken Usage</code></pre>\n<p>A useful trace could look like:</p>\n<pre><code>User Request\n    ↓\nLLM Call\n    ↓\nMCP Tool Discovery\n    ↓\nTool Call\n    ↓\nCRM API\n    ↓\nTool Result\n    ↓\nLLM Call\n    ↓\nFinal Response</code></pre>\n<p>Without tracing, debugging multi-server agent systems can become difficult.</p>\n<h1 id=\"mcp-doesn-t-replace-your-business-logic\">MCP Doesn&#39;t Replace Your Business Logic</h1>\n<p>This is another important principle.</p>\n<p>Suppose you have:</p>\n<pre><code>public RefundResult refundPayment(\n        String orderId,\n        BigDecimal amount) {\n    ...\n}</code></pre>\n<p>You shouldn&#39;t move all business logic into an MCP handler.</p>\n<p>Instead:</p>\n<pre><code>MCP Tool\n ↓\nApplication Service\n ↓\nBusiness Rules\n ↓\nRepository\n ↓\nDatabase</code></pre>\n<p>For example:</p>\n<pre><code>@McpTool(description = &quot;Refund an eligible order&quot;)\npublic RefundResult refundOrder(String orderId) {\n    return refundService.refund(orderId);\n}</code></pre>\n<p>The MCP layer becomes an interface.</p>\n<p>Your existing business service remains responsible for the actual business rules.</p>\n<p>This keeps the architecture clean.</p>\n<h1 id=\"mcp-as-an-integration-boundary\">MCP as an Integration Boundary</h1>\n<p>One of the strongest ways to think about MCP is as an integration boundary.</p>\n<p>Instead of:</p>\n<pre><code>AI\n ↓\nEverything</code></pre>\n<p>use:</p>\n<pre><code>AI\n ↓\nMCP\n ↓\nControlled Capabilities</code></pre>\n<p>The MCP layer becomes a contract between AI applications and external systems.</p>\n<p>For example:</p>\n<pre><code>AI Application\n      ↓\nMCP\n      ↓\nCRM</code></pre>\n<p>or:</p>\n<pre><code>AI Application\n      ↓\nMCP\n      ↓\nPayment System</code></pre>\n<p>or:</p>\n<pre><code>AI Application\n      ↓\nMCP\n      ↓\nInternal Developer Platform</code></pre>\n<h1 id=\"a-complete-spring-ai-mcp-architecture\">A Complete Spring AI + MCP Architecture</h1>\n<p>Now combine everything from this series:</p>\n<pre><code>                              User\n                                ↓\n                         Spring Boot API\n                                ↓\n                           ChatClient\n                                ↓\n                              Agent\n                                ↓\n             ┌──────────────────┼──────────────────┐\n             ↓                  ↓                  ↓\n           Memory              RAG              MCP Client\n             ↓                  ↓                  ↓\n         PostgreSQL          pgvector        ┌─────┼─────┐\n                                             ↓     ↓     ↓\n                                           CRM  GitHub  Search\n                                           MCP    MCP     MCP\n                                             ↓     ↓     ↓\n                                           APIs  APIs   APIs</code></pre>\n<p>Around the system:</p>\n<pre><code>Authentication\nAuthorization\nTenant Isolation\nObservability\nAudit Logging\nRate Limiting\nGuardrails\nHuman Approval</code></pre>\n<p>This is a strong foundation for production-oriented AI applications.</p>\n<h1 id=\"when-should-you-use-mcp\">When Should You Use MCP?</h1>\n<p>MCP becomes particularly useful when you have:</p>\n<pre><code>Multiple AI applications\n        ↓\nShared tools\n        ↓\nShared integrations</code></pre>\n<p>For example:</p>\n<pre><code>Sales Agent\nSupport Agent\nDeveloper Agent\nInternal Assistant</code></pre>\n<p>all need access to:</p>\n<pre><code>CRM\nGitHub\nInternal APIs\nDocumentation</code></pre>\n<p>Instead of implementing each integration separately:</p>\n<pre><code>Agent A → CRM Integration\nAgent B → CRM Integration\nAgent C → CRM Integration</code></pre>\n<p>you can create:</p>\n<pre><code>CRM MCP Server</code></pre>\n<p>and allow multiple AI applications to consume it.</p>\n<h1 id=\"when-you-don-t-need-mcp\">When You Don&#39;t Need MCP</h1>\n<p>MCP isn&#39;t automatically required for every AI application.</p>\n<p>If your application has:</p>\n<pre><code>One Agent\n ↓\nOne Tool\n ↓\nOne Internal Service</code></pre>\n<p>direct Spring AI tool calling may be simpler.</p>\n<p>For example:</p>\n<pre><code>ChatClient\n ↓\n@Tool\n ↓\nOrderService</code></pre>\n<p>Introducing an MCP server could add unnecessary infrastructure.</p>\n<p>A useful rule is:</p>\n<p>Use MCP when standardization, reuse, separation, or interoperability provides real value.</p>\n<p>Don&#39;t introduce another protocol simply because it is popular.</p>\n<h1 id=\"direct-tools-vs-mcp\">Direct Tools vs MCP</h1>\n<p>A simple comparison:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Approach</th><th>Best suited for</th></tr></thead><tbody><tr><td>Spring AI <code>@Tool</code></td><td>Local application capabilities</td></tr><tr><td>MCP</td><td>Shared/external capabilities</td></tr><tr><td>RAG</td><td>Knowledge retrieval</td></tr><tr><td>Memory</td><td>Conversation context</td></tr><tr><td>Agent</td><td>Dynamic decision-making</td></tr></tbody></table></div>\n<p>They are not mutually exclusive.</p>\n<p>A production system may use all of them:</p>\n<pre><code>Agent\n ├── Local Spring AI Tools\n ├── MCP Tools\n ├── RAG\n └── Memory</code></pre>\n<h1 id=\"the-bigger-picture\">The Bigger Picture</h1>\n<p>Our AI architecture has evolved throughout this series.</p>\n<p>We started with:</p>\n<pre><code>LLM\n ↓\nResponse</code></pre>\n<p>Then:</p>\n<pre><code>LLM\n ↓\nRAG\n ↓\nKnowledge</code></pre>\n<p>Then:</p>\n<pre><code>LLM\n ↓\nTools\n ↓\nActions</code></pre>\n<p>Then:</p>\n<pre><code>LLM\n ↓\nTools\n ↓\nMemory\n ↓\nAgent</code></pre>\n<p>And now:</p>\n<pre><code>Agent\n ↓\nMCP\n ↓\nExternal Capabilities</code></pre>\n<p>The architecture is becoming increasingly modular.</p>\n<h1 id=\"a-mental-model-for-mcp\">A Mental Model for MCP</h1>\n<p>Remember it this way:</p>\n<pre><code>LLM\n=\nReason\nRAG\n=\nRetrieve Knowledge\nMemory\n=\nRemember Context\nTool Calling\n=\nInvoke Capabilities\nMCP\n=\nStandardize Capability Access\nSpring Boot\n=\nBusiness Application\nAgent\n=\nCoordinate Decisions</code></pre>\n<p>Together:</p>\n<pre><code>LLM\n+\nRAG\n+\nMemory\n+\nTools\n+\nMCP\n+\nBusiness Logic\n=\nProduction AI Application</code></pre>\n<h1 id=\"final-takeaways\">Final Takeaways</h1>\n<p>MCP gives AI applications a standardized way to interact with external tools and resources.</p>\n<p>The key ideas are:</p>\n<ul><li></li></ul>\n<p><strong>MCP Client</strong> connects an AI application to MCP servers.</p>\n<ul><li></li></ul>\n<p><strong>MCP Server</strong> exposes tools, resources, and prompts.</p>\n<ul><li></li></ul>\n<p><strong>Tools</strong> allow actions to be performed.</p>\n<ul><li></li></ul>\n<p><strong>Resources</strong> provide access to information.</p>\n<ul><li></li></ul>\n<p><strong>Prompts</strong> can provide reusable prompt templates.</p>\n<ul><li>Spring AI supports MCP clients and servers through Boot starters and annotations.</li><li>MCP tools integrate with Spring AI&#39;s existing tool-calling architecture.</li><li>Streamable HTTP is the current HTTP-oriented transport in Spring AI 2.x.</li><li>MCP does not replace your business logic.</li><li>Authentication and authorization must be enforced before exposing network-accessible MCP servers.</li><li>Multi-tenant applications must preserve tenant isolation across MCP calls.</li><li>MCP is particularly useful when capabilities need to be shared across multiple AI applications.</li><li>For simple local integrations, direct Spring AI tools may be sufficient.</li></ul>\n<p>The architecture can now look like:</p>\n<pre><code>                         User\n                           ↓\n                        Agent\n                           ↓\n        ┌──────────────────┼──────────────────┐\n        ↓                  ↓                  ↓\n      Memory              RAG             MCP Client\n        ↓                  ↓                  ↓\n    Conversation       Vector DB       MCP Servers\n                                             ↓\n                              ┌──────────────┼──────────────┐\n                              ↓              ↓              ↓\n                             CRM          GitHub         Internal APIs</code></pre>\n<p>The important shift is this:</p>\n<pre><code>Before:\nAI Application\n ↓\nCustom Integrations\n ↓\nExternal Systems</code></pre>\n<p>With MCP:</p>\n<pre><code>AI Application\n ↓\nMCP Client\n ↓\nStandardized Protocol\n ↓\nMCP Servers\n ↓\nExternal Capabilities</code></pre>\n<p>MCP doesn&#39;t make your AI application automatically intelligent.</p>\n<p>It gives your AI application a <strong>standardized way to connect to capabilities</strong>.</p>\n<p>And when you combine MCP with Spring AI&#39;s:</p>\n<pre><code>ChatClient\n+\nTool Calling\n+\nRAG\n+\nMemory\n+\nAgents</code></pre>\n<p>you get a powerful foundation for building modular AI applications in Java.</p>\n<h2 id=\"what-s-next\">What&#39;s Next?</h2>\n<p>We&#39;ve now connected our AI agent to external capabilities.</p>\n<p>But another challenge appears:</p>\n<pre><code>One Agent\n      ↓\nMultiple MCP Servers\n      ↓\nMultiple Tools\n      ↓\nMultiple Decisions</code></pre>\n<p>How do we control which tools an agent can access?</p>\n<p>How do we handle permissions?</p>\n<p>How do we observe agent behavior?</p>\n<p>How do we evaluate whether an agent is making the right decisions?</p>\n<p>And how do we build reliable AI workflows instead of simply hoping the model does the right thing?</p>\n<p>That takes us into the next stage of AI engineering:</p>\n<p><strong>Building Production-Ready AI Agents with Spring AI — Guardrails, Evaluation, Observability, and Human-in-the-Loop Workflows.</strong></p>","headings":[{"level":1,"text":"Model Context Protocol with Spring AI: Building MCP Clients and Servers in Java","id":"model-context-protocol-with-spring-ai-building-mcp-clients-and-s"},{"level":1,"text":"What Is MCP?","id":"what-is-mcp"},{"level":1,"text":"Why MCP Exists","id":"why-mcp-exists"},{"level":1,"text":"MCP Is Not an LLM","id":"mcp-is-not-an-llm"},{"level":1,"text":"MCP Client vs MCP Server","id":"mcp-client-vs-mcp-server"},{"level":2,"text":"MCP Client","id":"mcp-client"},{"level":2,"text":"MCP Server","id":"mcp-server"},{"level":1,"text":"The Basic MCP Architecture","id":"the-basic-mcp-architecture"},{"level":1,"text":"MCP and Traditional Tool Calling","id":"mcp-and-traditional-tool-calling"},{"level":1,"text":"MCP Tools","id":"mcp-tools"},{"level":1,"text":"MCP Resources","id":"mcp-resources"},{"level":1,"text":"MCP Prompts","id":"mcp-prompts"},{"level":1,"text":"Building an MCP Server with Spring AI","id":"building-an-mcp-server-with-spring-ai"},{"level":1,"text":"Creating the MCP Server","id":"creating-the-mcp-server"},{"level":1,"text":"What Happens Inside the MCP Server?","id":"what-happens-inside-the-mcp-server"},{"level":1,"text":"Building an MCP Client","id":"building-an-mcp-client"},{"level":1,"text":"Connecting MCP Tools to ChatClient","id":"connecting-mcp-tools-to-chatclient"},{"level":1,"text":"MCP Tool Discovery","id":"mcp-tool-discovery"},{"level":1,"text":"Multiple MCP Servers","id":"multiple-mcp-servers"},{"level":1,"text":"MCP + Spring AI Agents","id":"mcp-spring-ai-agents"},{"level":1,"text":"MCP vs Direct Tool Calling","id":"mcp-vs-direct-tool-calling"},{"level":2,"text":"Direct Spring AI Tool","id":"direct-spring-ai-tool"},{"level":2,"text":"MCP Tool","id":"mcp-tool"},{"level":1,"text":"MCP Server as a Capability Layer","id":"mcp-server-as-a-capability-layer"},{"level":1,"text":"MCP + RAG","id":"mcp-rag"},{"level":1,"text":"MCP + Memory","id":"mcp-memory"},{"level":1,"text":"MCP Transports","id":"mcp-transports"},{"level":1,"text":"STDIO vs HTTP","id":"stdio-vs-http"},{"level":3,"text":"STDIO","id":"stdio"},{"level":3,"text":"Streamable HTTP","id":"streamable-http"},{"level":3,"text":"Stateless Streamable HTTP","id":"stateless-streamable-http"},{"level":1,"text":"MCP Security","id":"mcp-security"},{"level":1,"text":"MCP Tool Authorization","id":"mcp-tool-authorization"},{"level":1,"text":"MCP and Multi-Tenant Systems","id":"mcp-and-multi-tenant-systems"},{"level":1,"text":"MCP Error Handling","id":"mcp-error-handling"},{"level":1,"text":"MCP Observability","id":"mcp-observability"},{"level":1,"text":"MCP Doesn't Replace Your Business Logic","id":"mcp-doesn-t-replace-your-business-logic"},{"level":1,"text":"MCP as an Integration Boundary","id":"mcp-as-an-integration-boundary"},{"level":1,"text":"A Complete Spring AI + MCP Architecture","id":"a-complete-spring-ai-mcp-architecture"},{"level":1,"text":"When Should You Use MCP?","id":"when-should-you-use-mcp"},{"level":1,"text":"When You Don't Need MCP","id":"when-you-don-t-need-mcp"},{"level":1,"text":"Direct Tools vs MCP","id":"direct-tools-vs-mcp"},{"level":1,"text":"The Bigger Picture","id":"the-bigger-picture"},{"level":1,"text":"A Mental Model for MCP","id":"a-mental-model-for-mcp"},{"level":1,"text":"Final Takeaways","id":"final-takeaways"},{"level":2,"text":"What's Next?","id":"what-s-next"}]}}