Spring Boot Tutorial
Model Context Protocol (MCP) Servers and Clients
Tool calling lets a model use Java methods inside your own application. But what if you want the same tools available to Claude Desktop, an IDE assistant, or another team's AI application? Writing a custom integration for every AI client does not scale. The Model Context Protocol (MCP) is an open standard that solves this: an MCP server exposes tools, resources and prompts in a standard way, and any MCP client can discover and use them.
Spring AI 2.0 ships with the MCP Java SDK 2.0 and Boot starters for both sides. In this lesson you will expose Spring beans as MCP tools with @McpTool, run the server over Streamable HTTP, connect a Spring AI application to MCP servers as a client, and secure the setup.
MCP Concepts
An MCP server can offer three kinds of capability:
- Tools — functions the model can call, like "search orders" or "create Jira ticket".
- Resources — read-only data identified by a URI, such as a file, a database record or a configuration document.
- Prompts — reusable prompt templates the client can offer to users.
- Transports: STDIO (the client launches the server as a local process) and Streamable HTTP (the server runs as a web service; the default in Spring AI 2.0). SSE is the older HTTP transport.
Building an MCP Server with Spring Boot
Add spring-ai-starter-mcp-server-webmvc (or -webflux, or spring-ai-starter-mcp-server for STDIO). Annotate bean methods with @McpTool and parameters with @McpToolParam; the annotation scanner registers them and generates JSON schemas automatically. @McpResource and @McpPrompt expose resources and prompts the same way. Set spring.ai.mcp.server.protocol=STREAMABLE and the server listens on /mcp.
Consuming MCP Servers from Spring AI
Add spring-ai-starter-mcp-client and list the servers under spring.ai.mcp.client.streamable-http.connections or spring.ai.mcp.client.stdio.connections. At startup Spring AI connects, lists the tools on each server, and exposes them as a ToolCallbackProvider bean (SyncMcpToolCallbackProvider). Pass its callbacks to your ChatClient and the model can use remote tools exactly like local ones.
Security Considerations
An MCP server is an API that lets AI clients take actions, so secure it like any other API: require OAuth2 bearer tokens or API keys (Spring AI's MCP security support integrates with Spring Security), apply per-tool authorization, validate arguments, and log calls. On the client side, only connect to servers you trust — a malicious server can return tool descriptions or results containing prompt-injection instructions. For STDIO servers, remember that you are running someone else's program on your machine.
Examples
An MCP server exposing course catalogue tools
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
# application.yml
spring:
ai:
mcp:
server:
name: webnest-courses
version: 1.0.0
type: SYNC
protocol: STREAMABLE
server:
port: 8090
@Component
public class CourseCatalogTools {
private final CourseRepository courses;
public CourseCatalogTools(CourseRepository courses) {
this.courses = courses;
}
@McpTool(name = "search_courses",
description = "Search Webnest courses by keyword and return title, level and lesson count")
public List<CourseSummary> searchCourses(
@McpToolParam(description = "Keyword such as 'spring' or 'sql'", required = true) String keyword) {
return courses.searchByKeyword(keyword).stream()
.map(c -> new CourseSummary(c.getSlug(), c.getTitle(), c.getLevel(), c.getLessonCount()))
.toList();
}
@McpTool(name = "course_outline", description = "Get the module and lesson outline of a course by slug")
public CourseOutline outline(
@McpToolParam(description = "Course slug, e.g. spring-boot", required = true) String slug) {
return courses.outline(slug);
}
}
public record CourseSummary(String slug, String title, String level, int lessons) {}
Started McpServerApplication ... Tomcat started on port 8090
Registered MCP tools: [search_courses, course_outline]
MCP endpoint: http://localhost:8090/mcp (Streamable HTTP)
Exposing a resource and a prompt
@Component
public class CourseResources {
@McpResource(uri = "webnest://syllabus/{slug}", name = "Course syllabus",
description = "Markdown syllabus of a Webnest course")
public String syllabus(String slug) {
return syllabusService.markdownFor(slug);
}
@McpPrompt(name = "study_plan", description = "Create a weekly study plan for a course")
public GetPromptResult studyPlan(
@McpArg(name = "slug", description = "Course slug", required = true) String slug,
@McpArg(name = "hoursPerWeek", description = "Available hours per week", required = true) String hours) {
String text = "Create a week-by-week study plan for the course '" + slug
+ "' for a learner with " + hours + " hours per week.";
return GetPromptResult.builder(List.of(new PromptMessage(Role.USER, TextContent.builder(text).build())))
.description("Study plan")
.build();
}
}
Client lists resources -> webnest://syllabus/{slug}
Client lists prompts -> study_plan(slug, hoursPerWeek)
A Spring AI application using remote MCP tools
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
# application.yml of the assistant app
spring:
ai:
mcp:
client:
type: SYNC
request-timeout: 30s
streamable-http:
connections:
courses:
url: http://localhost:8090
endpoint: /mcp
stdio:
connections:
filesystem:
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "C:/webnest/notes"
@RestController
public class AdvisorController {
private final ChatClient chatClient;
public AdvisorController(ChatClient.Builder builder, SyncMcpToolCallbackProvider mcpTools) {
this.chatClient = builder
.defaultSystem("You are a course advisor for Webnest Studio.")
.defaultTools(mcpTools.getToolCallbacks())
.build();
}
@GetMapping("/advisor")
public String advise(@RequestParam String goal) {
return chatClient.prompt().user(goal).call().content();
}
}
GET /advisor?goal=I know Java and want to build secure REST APIs
(model calls search_courses("spring") then course_outline("spring-boot") on the MCP server)
Start with the Spring Boot course: work through "API Development", then the full
"Spring Security" module (13 lessons), finishing with "Testing secured endpoints".
Testing the server with the MCP Inspector
# Official MCP debugging UI (requires Node.js)
npx @modelcontextprotocol/inspector
# In the Inspector UI:
# Transport: Streamable HTTP
# URL: http://localhost:8090/mcp
# -> Connect -> Tools -> search_courses -> keyword = "spring" -> Run
[{"slug":"spring-framework","title":"Spring Framework","level":"intermediate to advanced","lessons":27},
{"slug":"spring-boot","title":"Spring Boot","level":"intermediate to advanced","lessons":100}]
Common Mistakes
- Exposing an MCP server over HTTP with no authentication, allowing anyone to call its tools.
- Connecting a client to untrusted third-party MCP servers whose tool descriptions can inject instructions into your model.
- Writing vague @McpTool descriptions — MCP clients rely entirely on them to pick tools.
- Using the older SSE transport for new servers when Streamable HTTP is now the default and recommended option.
- Returning huge payloads from tools and resources, which blows up the client model's context window.
Key Points to Remember
- MCP standardises how AI applications discover and use tools, resources and prompts from servers.
- spring-ai-starter-mcp-server-webmvc plus @McpTool/@McpResource/@McpPrompt turns Spring beans into an MCP server.
- Streamable HTTP is the default transport in Spring AI 2.0; STDIO suits local, process-launched servers.
- spring-ai-starter-mcp-client connects to servers and exposes their tools as a ToolCallbackProvider for ChatClient.
- Secure MCP servers with OAuth2 or API keys and only trust servers you control.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.