知识体系——MCP(三)io.modelcontextprotocol.sdk(1)开发mcp server

基于JSON-RPC 实现mcp server,需要定义哪些接口?怎么实现?下面来看下。

JSON-RPC 接口定义

通常都是POST接口。

核心接口有三个:initialize、tools/list、tools/call

1、initialize

客户端启动时调用,初始化 MCP Server,获取服务器信息、可用功能列表、版本等。

请求:

复制代码
{
  "jsonrpc": "2.0",
  "method": "initialize",
  "params": {
    "clientName": "example-client",
    "clientVersion": "1.0.0"
  },
  "id": 1
}

响应:

复制代码
{
  "jsonrpc": "2.0",
  "result": {
    "serverName": "mcp-server",
    "serverVersion": "1.0.0",
    "capabilities": ["tools/list", "tools/call"]
  },
  "id": 1
}

2、tools/list

列出 MCP Server 可用的工具或方法,也就是客户端可调用的能力。

请求:

复制代码
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": {},
  "id": 2
}

响应:

复制代码
{
  "jsonrpc": "2.0",
  "result": [
    {"name": "getUserById", "description": "获取用户信息", "params": ["id"]},
    {"name": "createOrder", "description": "创建订单", "params": ["userId", "items"]}
  ],
  "id": 2
}

3、tools/call

客户端调用 MCP Server 提供的具体工具/方法。

请求:

复制代码
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "toolName": "getUserById",
    "args": {"id": 123}
  },
  "id": 3
}

响应

复制代码
{
  "jsonrpc": "2.0",
  "result": {"id": 123, "name": "Zhangsan", "age": 30},
  "id": 3
}

4、其他接口

虽然最核心是上面三个接口,但根据 MCP Server 的复杂度,也可以增加:

接口 作用
tools/subscribe 订阅某些事件或数据流(可选,支持 SSE/WebSocket)
tools/unsubscribe 取消订阅
tools/health 获取 MCP Server 健康状态、运行信息(可选)
tools/config 查询或更新 MCP Server 配置(管理员用,可选)

开发这些接口,你可以手动实现,也可以基于框架实现,框架有io.modelcontextprotocol.sdk、langchan4j等。这里看io.modelcontextprotocol.sdk

一、手动写接口

比较繁琐,通过一个demo来看下

ai-demo-mcp-server/

├── pom.xml # Maven 项目配置文件

├── src/

│ ├── main/

│ │ ├── java/

│ │ │ └── com/

│ │ │ └── demo/

│ │ │ └── mcp/

│ │ │ ├── DemoMcpServer.java # 主入口类(支持 stdio 和 HTTP 模式)

│ │ │ │

│ │ │ ├── annotation/

│ │ │ │ └── Tool.java # @Tool 注解定义

│ │ │ │

│ │ │ ├── framework/

│ │ │ │ └── McpServer.java # MCP 服务器核心框架(工具扫描和注册)

│ │ │ │

│ │ │ ├── protocol/

│ │ │ │ ├── JsonRpcHandler.java # JSON-RPC 2.0 协议处理器

│ │ │ │ └── McpProtocolHandler.java # MCP 协议处理器(initialize, tools/list, tools/call)

│ │ │ │

│ │ │ ├── server/

│ │ │ │ └── McpHttpServer.java # HTTP 服务器包装(支持远程访问)

│ │ │ │

│ │ │ └── tools/

│ │ │ ├── EmployeeTool.java # 员工搜索工具示例

│ │ │ └── SearchLinkTool.java # 文档链接搜索工具示例

│ │ │

│ │ └── resources/

│ │ └── application.properties # 应用配置文件

pom

复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>ai-demo-mcp-server</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.2.0</spring-boot.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <mcp.version>0.1.3</mcp.version>
    </properties>

<!--    <properties>-->
<!--        <java.version>17</java.version>-->
<!--        <spring-boot.version>3.3.1</spring-boot.version>-->
<!--       -->
<!--    </properties>-->

    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <!-- Jackson for JSON processing -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>
        
        <!-- Note: com.openai:mcp-sdk is not available in public Maven repositories -->
        <!-- Using manual JSON-RPC implementation instead -->
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.demo.mcp.DemoMcpServer</mainClass>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                    <parameters>true</parameters>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

配置文件properties

复制代码
# Server Configuration
server.port=8088
spring.application.name=ai-demo-mcp-server

# MCP HTTP Server Configuration
mcp.http.enabled=true
mcp.http.port=8080

# Logging
logging.level.com.demo.mcp=DEBUG
logging.level.org.springframework=INFO

启动类

复制代码
package com.demo.mcp;

import com.demo.mcp.framework.McpServer;
import com.demo.mcp.server.McpHttpServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.util.Arrays;

/**
 * MCP 服务器主入口
 * 支持两种模式:
 * 1. stdio 模式(默认):通过标准输入输出通信
 * 2. http 模式:通过 HTTP 协议通信
 */
@SpringBootApplication
public class DemoMcpServer {
    
    public static void main(String[] args) {
        // 解析命令行参数
        boolean httpMode = Arrays.asList(args).contains("--http");
        boolean stdioMode = !httpMode || Arrays.asList(args).contains("--stdio");
        
        // 启动 Spring 应用
        ConfigurableApplicationContext context = SpringApplication.run(DemoMcpServer.class, args);
        
        try {
            McpServer mcpServer = context.getBean(McpServer.class);
            McpHttpServer httpServer = context.getBean(McpHttpServer.class);
            
            // 注册关闭钩子
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                httpServer.stop();
                context.close();
            }));
            
            if (stdioMode) {
                // stdio 模式:通过标准输入输出通信(在单独线程中运行)
                System.out.println("Starting MCP Server in stdio mode...");
                System.out.println("Server is ready. Waiting for JSON-RPC requests on stdin...");
                
                if (httpMode) {
                    // 如果同时启用 HTTP 模式,stdio 在后台线程运行
                    Thread stdioThread = new Thread(() -> {
                        try {
                            mcpServer.start();
                        } catch (Exception e) {
                            System.err.println("Stdio mode error: " + e.getMessage());
                            e.printStackTrace();
                        }
                    });
                    stdioThread.setDaemon(true);
                    stdioThread.start();
                } else {
                    // 仅 stdio 模式,在主线程运行
                    mcpServer.start();
                }
            }
            
            if (httpMode) {
                // HTTP 模式:启动 HTTP 服务器
                System.out.println("Starting MCP Server in HTTP mode...");
                httpServer.start();
                
                // 保持运行(等待中断信号)
                Thread.currentThread().join();
            }
        } catch (Exception e) {
            System.err.println("Failed to start MCP Server: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}

tools

复制代码
package com.demo.mcp.tools;

import com.demo.mcp.annotation.Tool;
import org.springframework.stereotype.Component;
import java.util.Map;

@Component
public class EmployeeTool {
    private static final Map<String, String> DB = Map.of(
        "zhangsan", "EMP-1001", 
        "lisi", "EMP-1002"
    );

    @Tool(name = "searchEmployee", description = "Search employee ID by employee name")
    public String searchEmployee(String name) {
        String result = DB.get(name);
        if (result != null) {
            return result;
        }
        return "Employee not found: " + name;
    }
}

package com.demo.mcp.tools;

import com.demo.mcp.annotation.Tool;
import org.springframework.stereotype.Component;
import java.util.Map;

@Component
public class SearchLinkTool {
    private static final Map<String, String> DB = Map.of(
        "employee_handbook", "https://example.com/shouce.pdf",
        "privacy_policy", "https://example.com/xieyi.pdf",
        "user_guide", "https://example.com/guide.pdf"
    );

    @Tool(name = "searchLink", description = "Search document link by document name")
    public String searchLink(String documentName) {
        String result = DB.get(documentName);
        if (result != null) {
            return result;
        }
        return "Document not found: " + documentName;
    }
}

annotation

复制代码
package com.demo.mcp.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Tool 注解
 * 用于标记 MCP 工具方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tool {
    /**
     * 工具名称
     */
    String name() default "";
    
    /**
     * 工具描述
     */
    String description() default "";
}

server

McpHttpServer
复制代码
package com.demo.mcp.server;

import com.demo.mcp.framework.McpServer;
import com.demo.mcp.protocol.JsonRpcHandler;
import com.demo.mcp.protocol.McpProtocolHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

/**
 * MCP HTTP 服务器
 * 支持通过 HTTP 协议访问 MCP 服务器
 */
@Component
public class McpHttpServer {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Autowired
    @Lazy
    private McpServer mcpServer;
    
    private HttpServer httpServer;
    
    @Value("${mcp.http.port:8080}")
    private int httpPort;
    
    @Value("${mcp.http.enabled:false}")
    private boolean httpEnabled;
    
    /**
     * 启动 HTTP 服务器
     */
    public void start() throws IOException {
        if (!httpEnabled) {
            return;
        }
        
        httpServer = HttpServer.create(new InetSocketAddress(httpPort), 0);
        httpServer.setExecutor(Executors.newCachedThreadPool());
        
        // 处理 JSON-RPC 请求
        httpServer.createContext("/mcp", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                String method = exchange.getRequestMethod();
                if ("POST".equals(method)) {
                    handlePost(exchange);
                } else if ("OPTIONS".equals(method)) {
                    // 处理 CORS 预检请求
                    handleOptions(exchange);
                } else {
                    sendResponse(exchange, 405, "Method Not Allowed");
                }
            }
        });
        
        // 健康检查端点
        httpServer.createContext("/health", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                String response = "{\"status\":\"ok\"}";
                sendResponse(exchange, 200, response);
            }
        });
        
        httpServer.start();
        System.out.println("MCP HTTP Server started on port " + httpPort);
    }
    
    /**
     * 处理 OPTIONS 请求(CORS 预检)
     */
    private void handleOptions(HttpExchange exchange) throws IOException {
        exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
        exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
        exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type");
        exchange.sendResponseHeaders(200, 0);
        exchange.close();
    }
    
    /**
     * 处理 POST 请求
     */
    private void handlePost(HttpExchange exchange) throws IOException {
        try (InputStream is = exchange.getRequestBody()) {
            String requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);
            
            // 解析 JSON-RPC 请求
            @SuppressWarnings("unchecked")
            Map<String, Object> request = objectMapper.readValue(requestBody, Map.class);
            
            // 创建协议处理器
            McpProtocolHandler mcpHandler = new McpProtocolHandler(
                mcpServer, "ai-demo-mcp-server", "1.0.0");
            
            // 处理请求
            Map<String, Object> response = mcpHandler.handleRequest(request);
            
            // 返回响应
            String responseJson = objectMapper.writeValueAsString(response);
            sendResponse(exchange, 200, responseJson);
        } catch (Exception e) {
            Map<String, Object> errorResponse = JsonRpcHandler.createErrorResponse(
                null, -32603, "Internal error: " + e.getMessage());
            String responseJson = objectMapper.writeValueAsString(errorResponse);
            sendResponse(exchange, 500, responseJson);
        }
    }
    
    /**
     * 发送 HTTP 响应
     */
    private void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException {
        exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
        exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
        exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
        exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type");
        
        byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
        exchange.sendResponseHeaders(statusCode, responseBytes.length);
        
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(responseBytes);
        }
    }
    
    /**
     * 停止 HTTP 服务器
     */
    public void stop() {
        if (httpServer != null) {
            httpServer.stop(0);
            System.out.println("MCP HTTP Server stopped");
        }
    }
}
McpServer
复制代码
package com.demo.mcp.framework;

import com.demo.mcp.annotation.Tool;
import com.demo.mcp.protocol.JsonRpcHandler;
import com.demo.mcp.protocol.McpProtocolHandler;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * MCP 服务器框架
 * 使用协议处理器分离关注点,代码更清晰易维护
 * 开发者只需要 @Tool 注解即可
 */
@Component
public class McpServer implements ApplicationContextAware, McpProtocolHandler.ToolRegistry {

    private final Map<String, ToolInfo> tools = new ConcurrentHashMap<>();
    private ApplicationContext applicationContext;
    private JsonRpcHandler jsonRpcHandler;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    /**
     * 在所有 bean 创建完成后初始化
     * 使用 @PostConstruct 避免循环依赖问题
     */
    @PostConstruct
    public void init() {
        scanTools();
        
        // 初始化协议处理器
        McpProtocolHandler mcpHandler = new McpProtocolHandler(
            this, "ai-demo-mcp-server", "1.0.0");
        this.jsonRpcHandler = new JsonRpcHandler(mcpHandler);
    }

    /**
     * 启动 MCP 服务器
     * 使用协议处理器处理所有 JSON-RPC 协议细节
     */
    public void start() {
        jsonRpcHandler.start();
    }

    /**
     * 扫描所有带 @Tool 注解的方法并注册
     */
    private void scanTools() {
        for (String beanName : applicationContext.getBeanNamesForType(Object.class)) {
            // 跳过自己和 HTTP 服务器,避免循环依赖
            if ("mcpServer".equals(beanName) || "mcpHttpServer".equals(beanName)) continue;
            
            Object bean = applicationContext.getBean(beanName);
            Class<?> clazz = bean.getClass();
            
            // 跳过 Spring 框架类
            if (clazz.getName().startsWith("org.springframework")) continue;
            
            for (Method method : clazz.getDeclaredMethods()) {
                Tool annotation = method.getAnnotation(Tool.class);
                if (annotation != null) {
                    String toolName = annotation.name().isEmpty() ? method.getName() : annotation.name();
                    tools.put(toolName, new ToolInfo(bean, method, annotation.description(), method.getParameters()));
                }
            }
        }
    }
    
    /**
     * 实现 ToolRegistry 接口:列出所有工具
     */
    @Override
    public List<Map<String, Object>> listTools() {
        List<Map<String, Object>> toolList = new ArrayList<>();
        for (ToolInfo info : tools.values()) {
            toolList.add(createToolSchema(info));
        }
        return toolList;
    }
    
    /**
     * 实现 ToolRegistry 接口:调用工具
     */
    @Override
    public String callTool(String toolName, Map<String, Object> arguments) throws Exception {
        ToolInfo info = tools.get(toolName);
        if (info == null) {
            throw new RuntimeException("Unknown tool: " + toolName);
        }
        
        // 准备方法参数
        Object[] args = new Object[info.method.getParameterCount()];
        for (int i = 0; i < args.length; i++) {
            String paramName = info.parameters[i].isNamePresent() 
                ? info.parameters[i].getName() 
                : "arg" + i;
            Object value = arguments.get(paramName);
            if (value == null) {
                throw new RuntimeException("Missing parameter: " + paramName);
            }
            args[i] = convertValue(value, info.method.getParameterTypes()[i]);
        }
        
        // 调用方法
        info.method.setAccessible(true);
        Object result = info.method.invoke(info.bean, args);
        return result != null ? result.toString() : "";
    }
    
    /**
     * 创建工具的 JSON Schema
     */
    private Map<String, Object> createToolSchema(ToolInfo info) {
        Map<String, Object> tool = new HashMap<>();
        tool.put("name", info.name);
        tool.put("description", info.description);
        
        Map<String, Object> inputSchema = new HashMap<>();
        inputSchema.put("type", "object");
        
        Map<String, Object> properties = new HashMap<>();
        List<String> required = new ArrayList<>();
        
        for (int i = 0; i < info.method.getParameterCount(); i++) {
            String paramName = info.parameters[i].isNamePresent() 
                ? info.parameters[i].getName() 
                : "arg" + i;
            Class<?> paramType = info.method.getParameterTypes()[i];
            
            properties.put(paramName, Map.of(
                "type", mapType(paramType),
                "description", paramName
            ));
            required.add(paramName);
        }
        
        inputSchema.put("properties", properties);
        inputSchema.put("required", required);
        tool.put("inputSchema", inputSchema);
        
        return tool;
    }


    private Object convertValue(Object value, Class<?> targetType) {
        if (targetType.isInstance(value)) return value;
        if (targetType == String.class) return value.toString();
        return value;
    }

    /**
     * 将 Java 类型映射为 JSON Schema 类型
     */
    private String mapType(Class<?> type) {
        if (type == String.class) return "string";
        if (type == Integer.class || type == int.class || type == Long.class || type == long.class) return "integer";
        if (type == Double.class || type == double.class || type == Float.class || type == float.class) return "number";
        if (type == Boolean.class || type == boolean.class) return "boolean";
        return "string";
    }

    private static class ToolInfo {
        final Object bean;
        final Method method;
        final String name;
        final String description;
        final java.lang.reflect.Parameter[] parameters;
        
        ToolInfo(Object bean, Method method, String description, java.lang.reflect.Parameter[] parameters) {
            this.bean = bean;
            this.method = method;
            this.name = method.getAnnotation(Tool.class).name().isEmpty() ? method.getName() : method.getAnnotation(Tool.class).name();
            this.description = description;
            this.parameters = parameters;
        }
    }
}
protocol
JsonRpcHandler
复制代码
package com.demo.mcp.protocol;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * JSON-RPC 2.0 协议处理器
 * 负责处理 stdio 通信和 JSON-RPC 协议的解析
 */
public class JsonRpcHandler {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RequestHandler requestHandler;
    
    public JsonRpcHandler(RequestHandler requestHandler) {
        this.requestHandler = requestHandler;
    }
    
    /**
     * 启动 JSON-RPC 服务器,监听 stdio
     */
    public void start() {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(System.in, StandardCharsets.UTF_8));
             PrintWriter writer = new PrintWriter(System.out, true, StandardCharsets.UTF_8)) {
            
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.trim().isEmpty()) continue;
                
                try {
                    String cleaned = cleanInput(line);
                    @SuppressWarnings("unchecked")
                    Map<String, Object> request = objectMapper.readValue(cleaned, Map.class);
                    Map<String, Object> response = requestHandler.handleRequest(request);
                    String responseJson = objectMapper.writeValueAsString(response);
                    writer.println(responseJson);
                } catch (Exception e) {
                    // JSON-RPC 2.0 错误响应
                    Map<String, Object> errorResponse = createErrorResponse(
                        null, -32700, "Parse error: " + e.getMessage());
                    writer.println(objectMapper.writeValueAsString(errorResponse));
                }
            }
        } catch (Exception e) {
            System.err.println("Fatal error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
    
    /**
     * 清理输入字符串(移除可能的引号)
     */
    private String cleanInput(String line) {
        String cleaned = line.trim();
        if ((cleaned.startsWith("'") && cleaned.endsWith("'")) ||
            (cleaned.startsWith("\"") && cleaned.endsWith("\""))) {
            cleaned = cleaned.substring(1, cleaned.length() - 1);
        }
        return cleaned;
    }
    
    /**
     * 创建 JSON-RPC 2.0 错误响应
     */
    public static Map<String, Object> createErrorResponse(Object id, int code, String message) {
        Map<String, Object> response = new java.util.HashMap<>();
        response.put("jsonrpc", "2.0");
        response.put("id", id);
        response.put("error", Map.of(
            "code", code,
            "message", message
        ));
        return response;
    }
    
    /**
     * 创建 JSON-RPC 2.0 成功响应
     */
    public static Map<String, Object> createSuccessResponse(Object id, Object result) {
        Map<String, Object> response = new java.util.HashMap<>();
        response.put("jsonrpc", "2.0");
        response.put("id", id);
        response.put("result", result);
        return response;
    }
    
    /**
     * 请求处理器接口
     */
    @FunctionalInterface
    public interface RequestHandler {
        Map<String, Object> handleRequest(Map<String, Object> request);
    }
}
McpProtocolHandler
复制代码
package com.demo.mcp.protocol;

import java.util.*;

/**
 * MCP 协议处理器
 * 处理 MCP 特定的协议方法(initialize, tools/list, tools/call)
 */
public class McpProtocolHandler implements JsonRpcHandler.RequestHandler {
    
    private final ToolRegistry toolRegistry;
    private final String serverName;
    private final String serverVersion;
    
    public McpProtocolHandler(ToolRegistry toolRegistry, String serverName, String serverVersion) {
        this.toolRegistry = toolRegistry;
        this.serverName = serverName;
        this.serverVersion = serverVersion;
    }
    
    @Override
    public Map<String, Object> handleRequest(Map<String, Object> request) {
        String method = (String) request.get("method");
        Object id = request.get("id");
        
        try {
            Object result;
            if ("initialize".equals(method)) {
                result = handleInitialize();
            } else if ("tools/list".equals(method)) {
                result = handleToolsList();
            } else if ("tools/call".equals(method)) {
                @SuppressWarnings("unchecked")
                Map<String, Object> params = (Map<String, Object>) request.get("params");
                result = handleToolsCall(params);
            } else {
                return JsonRpcHandler.createErrorResponse(id, -32601, "Method not found: " + method);
            }
            
            return JsonRpcHandler.createSuccessResponse(id, result);
        } catch (Exception e) {
            return JsonRpcHandler.createErrorResponse(id, -32603, "Internal error: " + e.getMessage());
        }
    }
    
    /**
     * 处理 initialize 请求
     */
    private Map<String, Object> handleInitialize() {
        Map<String, Object> result = new HashMap<>();
        result.put("protocolVersion", "2024-11-05");
        result.put("serverInfo", Map.of(
            "name", serverName,
            "version", serverVersion
        ));
        result.put("capabilities", Map.of(
            "tools", Map.of("listChanged", true)
        ));
        return result;
    }
    
    /**
     * 处理 tools/list 请求
     */
    private Map<String, Object> handleToolsList() {
        return Map.of("tools", toolRegistry.listTools());
    }
    
    /**
     * 处理 tools/call 请求
     */
    private Map<String, Object> handleToolsCall(Map<String, Object> params) throws Exception {
        String toolName = (String) params.get("name");
        @SuppressWarnings("unchecked")
        Map<String, Object> arguments = (Map<String, Object>) params.get("arguments");
        
        String result = toolRegistry.callTool(toolName, arguments);
        
        return Map.of(
            "content", List.of(Map.of(
                "type", "text",
                "text", result != null ? result : ""
            ))
        );
    }
    
    /**
     * 工具注册表接口
     */
    public interface ToolRegistry {
        List<Map<String, Object>> listTools();
        String callTool(String toolName, Map<String, Object> arguments) throws Exception;
    }
}

测试

(1)启动

输入:java -jar ai-demo-mcp-server-1.0.0.jar --stdio

复制代码
C:\mydemo\ai-demo-mcp-server\target>java -jar ai-demo-mcp-server-1.0.0.jar --stdio

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.0)

2025-12-01T10:50:51.642+08:00  INFO 28436 --- [ai-demo-mcp-server] [           main] com.demo.mcp.DemoMcpServer               : Starting DemoMcpServer using Java 21.0.8 with PID 28436 (C:\mydemo\ai-demo-mcp-server\target\ai-demo-mcp-server-1.0.0.jar started by Tina.Zhang in C:\mydemo\ai-demo-mcp-server\target)
2025-12-01T10:50:51.643+08:00 DEBUG 28436 --- [ai-demo-mcp-server] [           main] com.demo.mcp.DemoMcpServer               : Running with Spring Boot v3.2.0, Spring v6.1.1
2025-12-01T10:50:51.644+08:00  INFO 28436 --- [ai-demo-mcp-server] [           main] com.demo.mcp.DemoMcpServer               : No active profile set, falling back to 1 default profile: "default"
2025-12-01T10:50:52.218+08:00  INFO 28436 --- [ai-demo-mcp-server] [           main] com.demo.mcp.DemoMcpServer               : Started DemoMcpServer in 0.891 seconds (process running for 1.335)
Starting MCP Server in stdio mode...
Server is ready. Waiting for JSON-RPC requests on stdin...
(2)调用tool

输入:{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"searchEmployee","arguments":{"name":"zhangsan"}}}

输出:

(3)调用另外一个tool
复制代码
{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"searchLink","arguments":{"documentName":"employee_handbook"}}}

输出:

{"result":{"content":[{"type":"text","text":"https://example.com/shouce.pdf"}\]},"id":4,"jsonrpc":"2.0"}

输入不存在的:

(4)调用不存在的tool

二、io.modelcontextprotocol.sdk

MCP 官方 Java 开发框架io.modelcontextprotocol.sdk,非常容易开发mcp server

1、解决了什么问题

如果不用这个 SDK,你需要自己实现:

  1. JSON-RPC 2.0 协议

  2. MCP 方法规范

    • initialize

    • tools/list

    • tools/call

  3. 工具注册机制

  4. 参数 schema 定义

  5. SSE / STDIO / WebSocket 通信

  6. 请求路由

这些工作其实很繁琐。io.modelcontextprotocol.sdk 帮你封装好了。

2、sdk的核心能力

(1)MCP Server 构建

复制代码
McpServer server = McpServer.builder()
    .name("demo-server")
    .version("1.0.0")
    .build();

就可以快速构建 MCP Server,SDK 自动实现:

  • initialize

  • tools/list

  • tools/call

(2)Tool 注册

复制代码
server.addTool(
    Tool.builder()
        .name("getUser")
        .description("get user info")
        .handler(params -> {
            return Map.of("name", "zhangsan");
        })
        .build()
);

SDK 会自动:

  • 加入 tools/list

  • 处理 tools/call

  • 解析参数

  • 返回 JSON

(3)参数 Schema 自动生成

复制代码
Tool.builder()
    .name("getUser")
    .inputSchema(schema)

SDK 会把 schema 返回给客户端。

(4)通信方式支持

SDK 支持多种 MCP 通信方式:

通信方式 场景
STDIO 本地插件(Claude Desktop / Cursor)
SSE HTTP streaming
WebSocket 长连接
HTTP API 调用

如:

复制代码
McpServerTransport transport = new StdioServerTransport();
server.start(transport);
相关推荐
亚马逊云开发者3 小时前
人人都能写 OpenClaw Skill!手把手带你做一个自动日报技能
java
weixin_399380693 小时前
Prometheus(普罗米修斯)+grafana 监控Tongweb80909(by lqw)
java·grafana·prometheus
9527出列3 小时前
结合拦截器描述mybatis启动流程
java·mybatis
blues92573 小时前
MySQL 篇 - Java 连接 MySQL 数据库并实现数据交互
java·数据库·mysql
9527出列3 小时前
Redis系列--实现一个简单的redis分布式锁
java·redis
随机昵称_1234563 小时前
springboot导出带水印文字的xlsx
java·spring boot·后端
Lyyaoo.3 小时前
What is Maven?
java·spring boot·maven
23.3 小时前
【Java】NIO零拷贝:为何transferTo需要循环调用?
java·面试·nio
I_LPL3 小时前
day48 代码随想录算法训练营 图论专题1
java·算法·深度优先·图论·广度优先·求职面试