How to Build a Java MCP Server for AI Tools with Spring AI

This guide explains the Model Context Protocol (MCP), its JSON‑RPC communication modes, compares Java implementations, and provides a step‑by‑step tutorial—including project setup, configuration, tool creation, registration, and integration with Claude Desktop and IDEA—while covering description best practices, security, validation, and production considerations.

Java Web Project
Java Web Project
Java Web Project
How to Build a Java MCP Server for AI Tools with Spring AI

What problem MCP solves

Before MCP, each AI (Claude, GPT, Cursor) required its own plugin adapter for every business system, leading to an M×N explosion of adapters. MCP defines a standard protocol that reduces the integration effort to M+N, similar to the Language Server Protocol.

The protocol defines three capabilities: Tools (functions AI can call), Resources (data sources), and Prompts (template prompts). The article focuses on Tools, which cover 90% of business scenarios.

Underlying communication mechanism

MCP uses JSON‑RPC 2.0 and supports two transport modes:

stdio mode : the AI client launches the MCP server as a subprocess and communicates via standard input/output; ideal for local development with zero network configuration.

SSE mode : HTTP server‑sent events for remote deployment, allowing multiple clients to share a single MCP server.

A complete tool‑call flow consists of six steps:

AI client sends an initialize request when starting.

Server returns the list of supported Tools (name, description, parameter schema).

User initiates a conversation; the AI decides which Tool to call.

AI sends a tools/call request with the chosen Tool name and arguments.

Server executes the business logic and returns the result.

AI incorporates the result into its reply.

The description field of a Tool is critical; a well‑written description determines whether the AI can correctly understand and invoke the interface.

Java implementation options

Two mainstream choices exist in the Java ecosystem:

Spring AI MCP – maintained by the Spring team, version 1.0 stable, low integration difficulty (native Spring Boot), excellent documentation, best for existing Spring projects.

MCP4J – community‑driven, still active development, moderate integration difficulty, average documentation, suitable for standalone MCP services.

The tutorial adopts Spring AI MCP because most developers already use Spring Boot, resulting in zero additional learning cost.

Step‑by‑step: building an MCP Server

Project initialization

<dependencies>
  <!-- Spring AI MCP Server -->
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
  </dependency>

  <!-- Web (required for SSE mode) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>1.0.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

application.yml configuration

spring:
  ai:
    mcp:
      server:
        name: my-business-mcp-server
        version: 1.0.0
        # stdio mode
        transport: stdio
        # For SSE mode, change to:
        # transport: sse

Write the first Tool

Assume an order‑query service that Claude can call directly.

@Service
public class OrderService {
    private static final Map<String, Order> orders = Map.of(
        "ORD001", new Order("ORD001", "张三", 299.0, "PENDING"),
        "ORD002", new Order("ORD002", "李四", 599.0, "SHIPPED"),
        "ORD003", new Order("ORD003", "王五", 199.0, "DELIVERED")
    );

    /**
     * This annotation registers the method as an MCP Tool.
     * The description is crucial – AI decides when to call based on it.
     */
    @Tool(description = "根据订单ID查询订单信息,包括客户名称、金额和配送状态")
    public Order getOrderById(@ToolParam(description = "订单ID,格式为 ORD 开头加三位数字,例如 ORD001") String orderId) {
        Order order = orders.get(orderId);
        if (order == null) {
            throw new RuntimeException("订单不存在: " + orderId);
        }
        return order;
    }

    @Tool(description = "查询指定客户的所有订单列表")
    public List<Order> getOrdersByCustomer(@ToolParam(description = "客户姓名") String customerName) {
        return orders.values().stream()
            .filter(o -> o.getCustomerName().equals(customerName))
            .collect(Collectors.toList());
    }

    @Tool(description = "统计各状态订单数量,返回 PENDING/SHIPPED/DELIVERED 各自的数量")
    public Map<String, Long> getOrderStatistics() {
        return orders.values().stream()
            .collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));
    }
}

public record Order(String orderId, String customerName, Double amount, String status) {}

Register Tool to MCP Server

@Configuration
public class McpConfig {
    @Bean
    public ToolCallbackProvider orderTools(OrderService orderService) {
        // Spring AI automatically scans @Tool‑annotated methods
        return MethodToolCallbackProvider.builder()
            .toolObjects(orderService)
            .build();
    }
}

Launch class

@SpringBootApplication
public class McpServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }
}

Run mvn spring-boot:run and the MCP server starts.

Connect Claude Desktop for verification

Configuration file locations:

macOS:

~/Library/Application Support/Claude/claude_desktop_config.json

Windows: %APPDATA%\Claude\claude_desktop_config.json Add an entry to mcpServers:

{
  "mcpServers": {
    "my-order-service": {
      "command": "java",
      "args": ["-jar", "/your/project/path/target/mcp-server-1.0.0.jar"]
    }
  }
}

Restart Claude Desktop; the tool icon appears. Example conversation:

You: 帮我查一下 ORD002 这个订单的情况 Claude: 我来帮你查询一下。 [调用 getOrderById,参数:orderId="ORD002"] 查询结果: 订单号:ORD002 客户:李四 金额:¥599.0 状态:已发货(SHIPPED)

Connect IDEA Claude Code plugin

Add the same mcpServers entry to ~/.claude.json, then restart the Claude Code plugin in IDEA. The plugin automatically loads the MCP server.

Writing effective Tool descriptions

The AI decides whether to call a Tool solely based on the description field. A vague description leads to missed or wrong calls, while a detailed description guides the AI precisely.

Bad example :

@Tool(description = "查询订单")
public Order getOrderById(String orderId) { ... }

Problems: ambiguous purpose, no parameter details, no return information, no usage scenario.

Good example :

@Tool(description = """
    根据订单ID查询单个订单的详细信息。
    返回内容包括:客户姓名、订单金额、当前配送状态(PENDING/SHIPPED/DELIVERED)。
    当用户询问具体订单的状态、金额或客户信息时使用此工具。
    """)
public Order getOrderById(@ToolParam(description = "订单唯一标识符,格式为 ORD 开头加三位数字,例如 ORD001、ORD002") String orderId) { ... }

Key elements for a description (illustrated in a table in the original article) are:

Function statement : what the tool does.

Return content : which fields are returned.

Trigger scenario : when to use it.

Parameter format : constraints on inputs.

Boundary conditions : cases where the tool should not be used.

Production‑grade considerations

Permission control

@Tool(description = "查询订单信息")
public Order getOrderById(String orderId) {
    // Retrieve caller identity from MCP context (supported in Spring AI 1.0)
    // Apply business‑level permission checks
    validatePermission(orderId);
    return orderRepository.findById(orderId);
}

Parameter validation

@Tool(description = "查询订单")
public Order getOrderById(@ToolParam(description = "订单ID") String orderId) {
    // Format validation
    if (!orderId.matches("ORD\\d{3}")) {
        throw new IllegalArgumentException("订单ID格式错误: " + orderId);
    }
    return orderService.getById(orderId);
}

Return‑value design

Avoid returning JPA entities directly; instead return DTOs that contain only the fields the AI needs.

// Bad – returns JPA entity ❌
public OrderEntity getOrder(String id) { ... }

// Good – returns DTO ✅
public OrderDTO getOrder(String id) {
    OrderEntity entity = repository.findById(id);
    return OrderDTO.from(entity); // includes only AI‑relevant fields
}

Error handling

Provide human‑readable error messages so the AI can relay them to the user.

@Tool(description = "查询订单")
public Order getOrderById(String orderId) {
    return orderRepository.findById(orderId)
        .orElseThrow(() -> new RuntimeException(
            String.format("未找到订单 %s,请确认订单号是否正确", orderId)
        ));
}

Advanced: integrating a real database

Replace the in‑memory map with a JPA repository and richer DTOs.

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;

    @Tool(description = "查询订单详情。支持按订单ID精确查询。返回订单的完整信息包括商品列表、价格明细、配送地址和当前状态。")
    public OrderDetailDTO getOrderById(@ToolParam(description = "订单ID") String orderId) {
        return orderRepository.findById(orderId)
            .map(OrderDetailDTO::from)
            .orElseThrow(() -> new RuntimeException("订单不存在: " + orderId));
    }

    @Tool(description = "统计订单数据。可以按时间范围、状态、客户维度聚合。当用户询问最近多少天有多少订单、各状态订单分布等统计问题时使用。")
    public OrderStatDTO getStatistics(@ToolParam(description = "统计开始日期,格式 yyyy-MM-dd") String startDate,
                                      @ToolParam(description = "统计结束日期,格式 yyyy-MM-dd") String endDate) {
        LocalDate start = LocalDate.parse(startDate);
        LocalDate end = LocalDate.parse(endDate);
        return orderRepository.getStatistics(start, end);
    }
}

Full architecture recap

Your business system runs a Spring Boot MCP Server exposing Tools such as OrderService, UserService, and ReportService. Any AI client that supports MCP – Claude Desktop, IDEA + Claude Code, Cursor, or custom clients – can invoke these Tools directly.

Summary steps

Understand MCP protocol: JSON‑RPC 2.0 with stdio and SSE modes.

Use Spring AI MCP starter to create a server.

Annotate business methods with @Tool to expose them.

Write clear description strings so the AI can select the right Tool.

Integrate with Claude Desktop and IDEA Claude Code for verification.

In production, add permission checks, parameter validation, DTO return types, and user‑friendly error handling.

backendJavaAIMCPSpring Boot
Java Web Project
Written by

Java Web Project

Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.