VibeCoding之MCP(格式待调整版)
版本协商规则:
客户端发送自身支持的最新版本
服务器若支持该版本则原样返回;否则返回自身支持的最新版本
客户端若不接受服务器返回的版本,应主动断开连接
阶段 2:运行期(Operational)
客户端收到初始化响应后,必须发送 notifications/initialized 通知(无响应),之后进入正常交互阶段,可调用各类能力方法。
阶段 3:关闭(Shutdown)
客户端发送 shutdown 请求,服务器完成清理后响应
客户端收到响应后发送 notifications/exit 通知,正式结束连接
异常断开时,双方应能正确处理连接中断
完整 MCP 报文交互实例
以下是基于 stdio 传输、协议版本 2024-11-05 的一次完整生命周期交互,C 代表客户端(AI 宿主,如 Claude),S 代表 MCP 服务端。
- 初始化握手(必选,第一条消息)
C → S:客户端发起初始化请求
说明:客户端声明自身支持的协议版本、身份信息,以及自身具备的能力(采样、根目录访问等)。{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "clientInfo": { "name": "claude-desktop", "version": "1.10.0" }, "capabilities": { "sampling": {}, "roots": {} } } }
S → C:服务端返回初始化响应
说明:服务端确认协议版本,声明自身能力(此处仅声明支持工具调用),返回服务身份信息。{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "serverInfo": { "name": "java-demo-mcp", "version": "1.0.0" } } } - 初始化完成通知(必选,无响应)
C → S:客户端通知初始化流程完成
⚠️ 关键注意:这是通知消息,没有 id 字段,服务端收到后绝对不能返回响应,否则会破坏协议流。{ "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } - 获取工具列表
C → S:客户端查询所有可用工具
S → C:服务端返回工具元数据{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [ { "name": "greet", "description": "生成个性化问候语", "inputSchema": { "type": "object", "properties": { "name": { "type": "string", "description": "用户姓名" } }, "required": ["name"] } }, { "name": "calculate_add", "description": "计算两个整数的和", "inputSchema": { "type": "object", "properties": { "a": { "type": "number", "description": "第一个整数" }, "b": { "type": "number", "description": "第二个整数" } }, "required": ["a", "b"] } } ] } } - 调用工具(核心交互)
C → S:客户端调用指定工具
S → C:服务端返回工具执行结果{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "greet", "arguments": { "name": "张三" } } }
说明:content 是数组,支持多段内容;isError 为可选字段,标记执行是否出错。{ "jsonrpc": "2.0", "id": 3, "result": { "content": [ { "type": "text", "text": "你好,张三!欢迎使用 Java MCP 服务。" } ], "isError": false } } - 错误调用示例
C → S:调用不存在的工具
S → C:返回标准错误响应{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "not_exist_tool", "arguments": {} } }{ "jsonrpc": "2.0", "id": 4, "error": { "code": -32602, "message": "工具不存在: not_exist_tool" } } - 关闭流程
C → S:发起关闭请求
S → C:确认关闭{ "jsonrpc": "2.0", "id": 5, "method": "shutdown", "params": {} }
C → S:退出通知(无响应){ "jsonrpc": "2.0", "id": 5, "result": {} }
收到后服务端可安全退出进程。{ "jsonrpc": "2.0", "method": "notifications/exit", "params": {} }
如何写
mcp的出现让大模型有了直接调本地处理工具或第三方服务的能力
服务本地版(Java)
Java 原生手写 MCP(理解底层原理)
无需任何 MCP 框架,仅用 Jackson 处理 JSON,基于 stdio 实现完整协议,适合理解 MCP 底层逻辑。
- 依赖(Maven)
仅需 JSON 处理库:<dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.2</version> </dependency> </dependencies> - 完整可运行代码
运行 import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
public class RawJavaMcpServer {
private static final ObjectMapper objectMapper = new ObjectMapper();
// 协议版本:2024-11-05 为目前兼容性最广的稳定版
private static final String PROTOCOL_VERSION = "2024-11-05";
public static void main(String[] args) {
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
PrintWriter writer = new PrintWriter(System.out, true, StandardCharsets.UTF_8)
) {
String line;
// 循环读取标准输入,每行一个 JSON 请求
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
try {
JsonNode request = objectMapper.readTree(line);
JsonNode response = handleRequest(request);
// 通知类消息(无 id)不需要返回响应
if (response != null) {
writer.println(objectMapper.writeValueAsString(response));
writer.flush(); // 必须手动刷新缓冲区,否则消息不会发送
}
} catch (Exception e) {
// JSON 解析错误,返回标准错误响应
ObjectNode errorResp = objectMapper.createObjectNode();
errorResp.put("jsonrpc", "2.0");
errorResp.putNull("id");
ObjectNode error = errorResp.putObject("error");
error.put("code", -32700);
error.put("message", "Parse error: " + e.getMessage());
writer.println(objectMapper.writeValueAsString(errorResp));
writer.flush();
}
}
} catch (Exception e) {
// 异常日志必须输出到 stderr,绝对不能输出到 stdout,否则会破坏协议流
System.err.println("MCP Server 异常退出: " + e.getMessage());
}
}
/**
* 分发请求到对应处理方法
*/
private static JsonNode handleRequest(JsonNode request) {
String method = request.get("method").asText();
JsonNode id = request.get("id");
JsonNode params = request.has("params") ? request.get("params") : objectMapper.createObjectNode();
// 无 id 的是通知消息,无需响应
if (id == null || id.isNull()) {
return null;
}
return switch (method) {
case "initialize" -> handleInitialize(id, params);
case "tools/list" -> handleToolsList(id);
case "tools/call" -> handleToolCall(id, params);
default -> buildErrorResponse(id, -32601, "Method not found: " + method);
};
}
/**
* 1. 初始化处理:协商协议版本,声明服务能力
*/
private static JsonNode handleInitialize(JsonNode id, JsonNode params) {
ObjectNode result = objectMapper.createObjectNode();
result.put("protocolVersion", PROTOCOL_VERSION);
// 声明服务端能力:支持工具调用
ObjectNode capabilities = result.putObject("capabilities");
capabilities.putObject("tools");
// 服务端基本信息
ObjectNode serverInfo = result.putObject("serverInfo");
serverInfo.put("name", "java-raw-mcp-server");
serverInfo.put("version", "1.0.0");
return buildSuccessResponse(id, result);
}
/**
* 2. 工具列表:返回所有工具的名称、描述、参数规范
*/
private static JsonNode handleToolsList(JsonNode id) {
ObjectNode result = objectMapper.createObjectNode();
var tools = result.putArray("tools");
// 工具1:个性化问候
ObjectNode helloTool = tools.addObject();
helloTool.put("name", "hello");
helloTool.put("description", "向用户发送个性化问候语");
ObjectNode helloSchema = helloTool.putObject("inputSchema");
helloSchema.put("type", "object");
ObjectNode helloProps = helloSchema.putObject("properties");
ObjectNode nameProp = helloProps.putObject("name");
nameProp.put("type", "string");
nameProp.put("description", "用户的名字");
helloSchema.putArray("required").add("name");
// 工具2:加法计算
ObjectNode addTool = tools.addObject();
addTool.put("name", "add");
addTool.put("description", "计算两个整数的和");
ObjectNode addSchema = addTool.putObject("inputSchema");
addSchema.put("type", "object");
ObjectNode addProps = addSchema.putObject("properties");
addProps.putObject("a").put("type", "number").put("description", "第一个数字");
addProps.putObject("b").put("type", "number").put("description", "第二个数字");
addSchema.putArray("required").add("a").add("b");
return buildSuccessResponse(id, result);
}
/**
* 3. 工具调用:执行具体业务逻辑
*/
private static JsonNode handleToolCall(JsonNode id, JsonNode params) {
String toolName = params.get("name").asText();
JsonNode arguments = params.get("arguments");
try {
return switch (toolName) {
case "hello" -> {
String name = arguments.get("name").asText();
yield buildTextResponse(id, "你好," + name + "!欢迎使用 Java 原生 MCP 服务。");
}
case "add" -> {
int a = arguments.get("a").asInt();
int b = arguments.get("b").asInt();
yield buildTextResponse(id, a + " + " + b + " = " + (a + b));
}
default -> buildErrorResponse(id, -32602, "工具不存在: " + toolName);
};
} catch (Exception e) {
return buildErrorResponse(id, -32000, "工具执行异常: " + e.getMessage());
}
}
// 构建成功响应
private static ObjectNode buildSuccessResponse(JsonNode id, JsonNode result) {
ObjectNode response = objectMapper.createObjectNode();
response.put("jsonrpc", "2.0");
response.set("id", id);
response.set("result", result);
return response;
}
// 构建文本类型的工具调用结果
private static ObjectNode buildTextResponse(JsonNode id, String text) {
ObjectNode result = objectMapper.createObjectNode();
var content = result.putArray("content");
ObjectNode textItem = content.addObject();
textItem.put("type", "text");
textItem.put("text", text);
return buildSuccessResponse(id, result);
}
// 构建错误响应
private static ObjectNode buildErrorResponse(JsonNode id, int code, String message) {
ObjectNode response = objectMapper.createObjectNode();
response.put("jsonrpc", "2.0");
response.set("id", id);
ObjectNode error = response.putObject("error");
error.put("code", code);
error.put("message", message);
return response;
}
}
四、方式二:LangChain4j 快速实现(生产级推荐)
实际项目开发中,推荐使用成熟框架屏蔽底层协议细节。LangChain4j 是 Java 生态最完善的 AI 开发框架,原生支持 MCP 服务端 / 客户端。
1. 依赖(Maven)
```xml
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-mcp-server</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.17</version>
</dependency>
</dependencies>
- 业务工具类
只需用 @Tool 注解标记方法,框架自动生成工具描述和参数规范:运行 import dev.langchain4j.agent.tool.Tool;
public class CalculatorTools {
@Tool("计算两个整数的和")
public int add(int a, int b) {
return a + b;
}
@Tool("计算两个整数的乘积")
public int multiply(int a, int b) {
return a * b;
}
@Tool("生成个性化问候语")
public String hello(String name) {
return "你好," + name + "!这是 LangChain4j 实现的 MCP 服务。";
}
}
3. 服务启动类
```java
运行
import dev.langchain4j.community.mcp.server.McpServer;
import dev.langchain4j.community.mcp.server.transport.StdioMcpServerTransport;
import dev.langchain4j.mcp.protocol.McpImplementation;
import java.util.List;
public class LangChain4jMcpServer {
public static void main(String[] args) throws InterruptedException {
// 服务基本信息
McpImplementation serverInfo = new McpImplementation();
serverInfo.setName("java-langchain4j-mcp");
serverInfo.setVersion("1.0.0");
// 注册工具,创建 MCP 服务
McpServer server = new McpServer(
List.of(new CalculatorTools()),
serverInfo
);
// 启动 stdio 传输通道
new StdioMcpServerTransport(System.in, System.out, server);
// 保持进程常驻
Thread.currentThread().join();
}
}
框架会自动完成协议握手、工具列表生成、参数校验与解析、异常处理等所有底层工作,开发者只需专注业务逻辑。
五、运行与接入测试
- 本地手动验证
运行主程序后,在控制台输入初始化请求 JSON,可验证服务是否正常响应:{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test-client","version":"1.0.0"}}} - 接入 MCP 客户端(以 Claude Desktop 为例)
将项目打包为包含所有依赖的 fat jar(可使用 maven-assembly-plugin)
打开 Claude 配置文件,添加服务配置:{ "mcpServers": { "java-mcp-demo": { "command": "java", "args": ["-jar", "/你的jar包完整路径/mcp-server.jar"] } } }
服务云端部署版
如何加载
常见问题说明
服务端和客户端谁先启动?
SSE 远程模式:必须服务端先启动并监听端口,客户端启动后才能连接成功,否则会报连接失败
stdio 模式:客户端负责启动服务端进程,无需提前启动
配置了远程服务但找不到工具,怎么排查?
按优先级排查:是否重启客户端 → 服务器端口 / 网络是否通 → SSE 端点路径是否匹配 → 鉴权头是否正确 → 协议版本是否兼容
连接断开后会重新初始化吗?
会。网络波动导致 SSE 断开后,客户端自动重试重连,重连成功后会重新走完整的 initialize 初始化流程。
ai是如何选择要使用的mcp
AI 不会直接 “选择哪个 MCP 服务”,它选择的是具体的工具(Tool);MCP 服务是工具的载体,一个 MCP 服务可以包含多个工具。
整个选择逻辑可以概括为:
客户端提前聚合所有已配置 MCP 服务的工具列表
把工具的名称、描述、参数规范注入大模型上下文
大模型基于用户问题,通过 Function Calling 能力判断是否需要调用工具、调用哪个工具
客户端根据工具归属,路由到对应的 MCP 服务执行调用
下面按完整流程拆解细节。
一、完整选择与调用全流程
阶段 1:客户端侧的工具聚合(启动时完成)
在用户提问之前,客户端已经完成了所有准备工作,这一步和 AI 无关,是客户端的协议行为:
客户端启动后,读取所有 mcpServers 配置,逐个建立连接
对每个 MCP 服务调用 tools/list 接口,拉取该服务下全部工具的元数据(名称、描述、inputSchema)
客户端把所有服务的工具汇总成一张全局工具清单,记录每个工具归属的 MCP 服务
若工具名称重复,客户端通常会按服务名加前缀或直接报错去重
对应之前讲的 MCP 生命周期:初始化完成后,客户端会立刻拉取工具列表缓存起来,不会每次提问都重新拉取。
阶段 2:提问前的预筛选(可选,客户端实现)
当 MCP 工具数量很多(几十上百个)时,全部塞进上下文会占用大量 Token、干扰模型判断,因此主流客户端会做前置筛选,只把最相关的工具交给模型:
场景过滤:比如代码编辑器里只展示开发类工具,文档场景只展示文档类工具
语义召回:对用户问题做向量匹配,从全量工具中召回 Top N 个最相关的,再交给模型
权限过滤:根据当前用户 / 工作区权限,隐藏未授权的工具
用户手动开关:客户端通常提供工具启用 / 禁用开关,被禁用的工具不会进入模型视野
阶段 3:模型侧的决策(核心环节)
筛选后的工具列表会以系统提示词 / 函数定义的形式注入到本次对话的上下文中,大模型基于用户输入自主决策。
本质就是标准的 Function Calling 逻辑,模型会做三层判断:
要不要调用工具:判断用户问题仅凭模型自身知识能否回答,是否需要外部能力
调用哪个工具:匹配工具描述与用户意图,选择最契合的一个或多个工具
填充什么参数:从用户问题中提取参数,按 JSON Schema 格式生成入参
模型决策的内部逻辑
模型会把每个工具的 name + description 当作 “能力标签”,和用户的意图做语义匹配
描述越精准、和用户问题重合度越高,被选中的概率越大
若多个工具都能满足需求,模型会优先选择描述更匹配、参数更简单的那个
复杂问题下,模型可能选择多工具串行调用:先调用 A 工具拿到结果,再基于结果调用 B 工具
阶段 4:客户端路由执行
模型输出工具调用指令后,客户端执行最终路由:
解析模型返回的工具名和参数
根据全局工具清单,查到该工具属于哪个 MCP 服务
向对应 MCP 服务发起 tools/call 请求
拿到执行结果后,把结果塞回上下文,让模型基于结果继续生成回答
影响 AI 选择的关键因素
AI 选工具本质是语义匹配,你的 MCP 工具能不能被选中、会不会被误选,核心取决于 4 个要素:
- 工具描述(description)
这是影响选择的第一要素。
❌ 反面例子:“description”: “处理数据”(太模糊,模型无法判断适用场景)
✅ 正面例子:“description”: “查询指定服务器的CPU、内存、磁盘实时使用率,输入服务器IP地址”(场景、能力、入参都讲清楚)
描述越具体、越贴近真实使用场景,模型越容易在对应问题下选中它。 - 工具名称(name)
名称是语义匹配的强信号,模型会优先通过名称做初步匹配。
建议使用「动词 + 名词」的命名方式,比如 query_server_metrics、generate_excel_report
避免使用缩写、黑话、无意义的命名 - 参数 Schema 与参数描述
inputSchema 里每个字段的 description 也会影响模型判断,同时决定了模型能不能正确提取参数。
每个参数都要写清楚含义、格式要求(比如 “格式为 YYYY-MM-DD”、“单位为秒”)
必填项和可选项要明确区分
枚举值尽量列全,减少模型参数填充错误 - 工具排序与上下文位置
客户端传给模型的工具列表是有顺序的,排在前面的工具被选中的概率会略高。
多数客户端按 MCP 配置顺序 + 工具在 tools/list 中的返回顺序排列
做过语义预筛选的客户端,会把匹配度高的工具排在前面
常见的补充机制 - 用户手动干预
几乎所有 MCP 客户端都支持人工干预选择:
用户可以手动禁用某个 MCP 服务或单个工具,禁用后模型完全看不到
部分客户端支持「@工具」手动指定调用,强制模型使用某个工具
高危工具调用前会弹窗二次确认,用户同意后才会真正执行 - 多轮对话中的工具复用
在同一轮对话中,模型会记住已经调用过的工具,后续相关问题会优先复用已经用过的工具,而不是重新选一个新的。 - 错误重试与工具替换
如果调用某个工具失败、返回结果不符合预期,模型可能会:
调整参数重试同一个工具
换另一个功能相似的工具尝试
放弃调用工具,直接基于自身知识回答
四、优化建议:让你的 MCP 工具更容易被选中
如果你在开发 MCP 服务,可以按以下原则优化,提升工具被正确调用的概率:
工具名直白:用动宾结构,见名知意
描述写场景:不要只写 “做什么”,还要写 “适合什么时候用”
参数讲清楚:每个参数的含义、格式、取值范围都写完整
单一职责:一个工具只做一件事,不要做全能工具,否则描述会模糊,匹配度下降
避免重名:和常见工具(比如文件读写、搜索)区分开,防止被系统工具覆盖
更多推荐
所有评论(0)