知识体系(二)MCP
一、介绍
1、简介
MCP 在 LangChain4j / LangChain 体系中是一个新的重要概念,是由 Anthropic、LangChain 等生态共同提出的一种标准化协议,它定义了 模型与上下文(Context Providers)之间的通信方式。全称是:Model Context Protocol(模型上下文协议)。类比:MCP 是“大模型界的 JDBC”。
| 类比 | MCP 的作用 |
|---|---|
| 数据库驱动(JDBC) | 为不同数据库提供统一的访问协议 |
| LLM 的上下文协议(MCP) | 为不同上下文或工具提供统一的访问协议 |
2、作用
用于让 大模型(LLM) 与 外部工具、知识源、数据上下文 之间进行交互。你可以把它理解成:
“让模型能安全、通用地访问外部世界的统一接口标准。”
| 目标 | 说明 |
|---|---|
| 🧩 标准化接口 | 不同的上下文源都可以以统一的方式被模型访问 |
| 🔒 安全隔离 | 模型无法直接访问主机资源,而是通过受控的 MCP 服务 |
| 🔄 可扩展 | 可以方便地注册新的 MCP Provider(插件式) |
| 🤝 跨语言/框架 | 无论是 Python LangChain、Java LangChain4j、Node.js、Claude Desktop,都能用相同协议交互 |
3、背景
在传统的 LLM 调用中,模型只是接收 prompt → 输出文本。但在实际场景下,我们希望模型可以:
-
访问数据库或知识库;
-
调用 REST / GraphQL / 内部 API;
-
获取用户文档或上下文信息;
-
执行工具函数(Tool Call);
-
获取 embeddings、文件、搜索结果等。
而 MCP 就是为了解决这个“上下文接入问题” 而设计的协议。
4、工作机制
核心组成:
┌──────────────────────────────────┐
│ MCP Client(LLM端) │
│ ChatGPT、LangChain4j、Claude │
└──────────┬───────────────────────┘
│ Transport (WebSocket / stdio)
┌──────────▼────────────────────────┐
│ MCP Server(你写的工具端) │
│ - 提供 tools │
│ - 提供 resources │
│ - 提供 prompts │
└────────────────────────────────────┘
用户输入
↓
LangChain4j 调用 LLM Gateway (统一模型调用接口)
↓
模型分析出需要查询库存 → 发起 MCP 调用
↓
MCP Server 查询数据库/外部 API 返回库存
↓
LLM 综合信息生成最终回答
例如,当你用 LangChain4j 构建一个智能客服系统时:
-
LLM 需要查询数据库 → 它通过
McpClient调用 MCP; -
MCP Server 封装了数据库访问逻辑;
-
模型得到结果后生成自然语言回答。
5、和LLM gateway的区别
概括
| 概念 | 作用 | 类比 |
|---|---|---|
| 🧩 MCP(Model Context Protocol) | 让模型安全、统一地访问“外部数据、工具、上下文” | 模型的「数据/功能接口标准」 |
| 🌐 LLM Gateway(大模型网关) | 统一封装多个大模型(OpenAI、Claude、Gemini等)的调用 | 模型的「统一访问入口」 |
核心定位对比
| 对比维度 | MCP(Model Context Protocol) | LLM Gateway |
|---|
| 📌 主要作用 | 模型访问外部上下文、工具、数据的标准协议 | 多模型统一接入、负载与调用网关 |
| 🎯 面向对象 | 上下文(Context Provider) | 模型(Model Provider) |
| 💬 传输方向 | 模型 ←→ 外部服务(如DB、API、文件) | 应用 ←→ 模型(如OpenAI、Claude) |
| 🧱 协议形式 | 一种通用接口协议(HTTP/WebSocket) | 通常是 REST API 层的封装服务 |
| 🧩 类似于 | JDBC、GraphQL Resolver、Plugin API | Nginx for LLM、Model Router |
| 🔒 安全控制 | 控制模型可访问哪些外部资源 | 控制用户可访问哪些模型与额度 |
| 💼 使用者 | LangChain / LangChain4j / Claude | 企业内部 / SaaS 平台 |
| 🧰 典型实现 | LangChain MCP, Claude Desktop MCP | OpenAI Proxy, OneAPI, FastGPT Gateway, 自建 Model Router |
demo
(1)LLM Gateway 端(统一模型调用)
企业通常部署一个 LLM Gateway,比如在 http://llm.company.com
POST /v1/chat/completions
{
"model": "gpt-4",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "查下今天上海的库存"}
]
}
这个 Gateway 统一封装了:
-
OpenAI、Claude、Moonshot、文心一言等不同模型;
-
统一认证、监控、负载均衡。
伪代码:
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("https://llm.company.com/v1") // LLM Gateway 地址
.apiKey("corp-xxxxxx")
.modelName("gpt-4")
.build();
(2)MCP Server 端(提供外部工具)
你的 MCP 服务运行在 http://localhost:8081,它提供一个工具
伪代码:
@RestController
@RequestMapping("/mcp/tools")
public class InventoryMcpController {
@PostMapping("/getInventory")
public Map<String, Object> getInventory(@RequestBody Map<String, Object> params) {
String city = (String) params.get("city");
// 模拟外部数据库查询
String stock = switch (city) {
case "上海" -> "当前上海仓库有 128 台设备";
case "北京" -> "当前北京仓库有 87 台设备";
default -> "未找到该城市库存信息";
};
return Map.of("result", stock);
}
}
(3)LangChain4j 应用端(连接 Gateway + MCP)
-
OpenAiChatModel(或GatewayChatModel)访问 LLM Gateway; -
McpClient调用 MCP Server 工具; -
把它们注册给
AiAgent统一协调。
伪代码示例:
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.agent.AiAgent;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.model.McpResponse;
public class LlmMcpIntegrationExample {
public static void main(String[] args) {
// 1️⃣ LLM Gateway 客户端(统一模型入口)
OpenAiChatModel llm = OpenAiChatModel.builder()
.baseUrl("https://llm.company.com/v1") // Gateway 地址
.apiKey("corp-xxxxxx")
.modelName("gpt-4")
.build();
// 2️⃣ MCP 客户端(连接外部上下文)
McpClient mcp = new McpClient("http://localhost:8081", null);
// 3️⃣ 模拟 LLM 通过 MCP 调用工具
String userInput = "帮我查一下今天上海的库存情况";
// 实际上,LangChain4j 的 Agent 会自动判断是否需要调用 MCP;
// 这里我们手动模拟模型发起的 MCP 调用。
McpResponse inventoryResp = mcp.callTool("getInventory", Map.of("city", "上海"));
String inventoryResult = inventoryResp.getContent();
// 4️⃣ 把查询结果再喂回给模型
String finalPrompt = """
用户问题:%s
库存数据查询结果:%s
请用自然语言综合回答用户。
""".formatted(userInput, inventoryResult);
String answer = llm.generate(finalPrompt);
System.out.println("模型回答:" + answer);
// 5️⃣ 关闭连接
mcp.close();
}
}
6、和http协议的区别
(1)mcp
MCP(Model Context Protocol)= LLM 专用的双向协议,目的:
-
让 LLM 直接调用你的后端代码/工具
-
不需要你写 HTTP Controller
-
不需要你写 JSON API 文档
-
不需要你自己解析用户输入
-
全自动匹配工具、解析参数、调用方法、生成结果
你只需要像下面这样写,LLM 就能自动调用。
MCP Server 本质上是 一组方法(工具)+ 一个 LLM 可以访问的规范接口。
@Tool
public String searchEmployee(String name) { ... }
(2)普通 HTTP
-
你需要自己写 Controller
-
自己定义 Request/Response JSON
-
自己处理校验
-
用户调用必须写 curl 或前端代码
-
和 LLM 没有自动关系
@GetMapping("/employee")
public Result findEmployee(@RequestParam String name) { ... }
二、MCP 协议的两种传输方式
| 模式 | 调用方式 | 是否可命令行调用 |
|---|---|---|
| stdio(默认) | 本地命令行直接执行 | ✔ 可 |
| websocket | 通过 mcp-client 连接 | ✔ 可 |
| HTTP webhook | 不是 MCP 通道 | ✘ 不可 |
1、stdio 模式(命令行模式)
通过标准输入输出(stdin/stdout)通信;适合命令行直接调用;客户端通过管道启动服务器进程。
2、HTTP/SSE 模式(网络模式)
目前 MCP 正在快速普及,已被以下生态采用或支持:
Anthropic Claude 3.5+
LangChain / LangChain4j
OpenDevin / OpenInterpreter
Claude Desktop(本地 MCP 插件)
Ollama / LlamaIndex 也在逐步集成中
1、命令行
MCP Server 完全可以从命令行调用,但方式和调用 HTTP 不一样,不能使用curl。有几下几种方法:
(1)使用
mcp-client(最推荐)OpenAI 官方提供了一个命令行 MCP 客户端,可以直接连你的 MCP server。
npm install -g @modelcontextprotocol/client调用你的 MCP server(以 stdio 模式为例):当然,Spring Boot MCP Server 可以直接在命令行运行(java -jar my-mcp-server.jar)
mcp-client run --command "java -jar your-mcp-server.jar"然后可以执行:
tool call searchEmployee --params '{"name": "张三"}'2、客户端
用 Claude Desktop / ChatGPT Desktop(带 MCP 客户端)
3、自定义CLI 客户端
因为 MCP 使用的是 JSON-RPC over stdio 或 websocket,
你可以自己写一个 Java / Python CLI 客户端:java -jar mcp-client.jar --call searchEmployee '{"name":"张三"}'
三、demo
1、studio模式
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

更多推荐

所有评论(0)