基于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,你需要自己实现:
-
JSON-RPC 2.0 协议
-
MCP 方法规范
-
initialize -
tools/list -
tools/call
-
-
工具注册机制
-
参数 schema 定义
-
SSE / STDIO / WebSocket 通信
-
请求路由
这些工作其实很繁琐。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);