版本协商规则:
客户端发送自身支持的最新版本
服务器若支持该版本则原样返回;否则返回自身支持的最新版本
客户端若不接受服务器返回的版本,应主动断开连接
阶段 2:运行期(Operational)
客户端收到初始化响应后,必须发送 notifications/initialized 通知(无响应),之后进入正常交互阶段,可调用各类能力方法。
阶段 3:关闭(Shutdown)
客户端发送 shutdown 请求,服务器完成清理后响应
客户端收到响应后发送 notifications/exit 通知,正式结束连接
异常断开时,双方应能正确处理连接中断

完整 MCP 报文交互实例
以下是基于 stdio 传输、协议版本 2024-11-05 的一次完整生命周期交互,C 代表客户端(AI 宿主,如 Claude),S 代表 MCP 服务端。

  1. 初始化握手(必选,第一条消息)
    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"
    }
    }
    }
    
    说明:服务端确认协议版本,声明自身能力(此处仅声明支持工具调用),返回服务身份信息。
  2. 初始化完成通知(必选,无响应)
    C → S:客户端通知初始化流程完成
    {
    "jsonrpc": "2.0",
    "method": "notifications/initialized",
    "params": {}
    }
    
    ⚠️ 关键注意:这是通知消息,没有 id 字段,服务端收到后绝对不能返回响应,否则会破坏协议流。
  3. 获取工具列表
    C → S:客户端查询所有可用工具
    {
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
    }
    
    S → C:服务端返回工具元数据
    {
    "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"]
    }
    }
    ]
    }
    }
    
  4. 调用工具(核心交互)
    C → S:客户端调用指定工具
    {
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
    "name": "greet",
    "arguments": {
    "name": "张三"
    }
    }
    }
    
    S → C:服务端返回工具执行结果
    {
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
    "content": [
    {
    "type": "text",
    "text": "你好,张三!欢迎使用 Java MCP 服务。"
    }
    ],
    "isError": false
    }
    }
    
    说明:content 是数组,支持多段内容;isError 为可选字段,标记执行是否出错。
  5. 错误调用示例
    C → S:调用不存在的工具
    {
    "jsonrpc": "2.0",
    "id": 4,
    "method": "tools/call",
    "params": {
    "name": "not_exist_tool",
    "arguments": {}
    }
    }
    
    S → C:返回标准错误响应
    {
    "jsonrpc": "2.0",
    "id": 4,
    "error": {
    "code": -32602,
    "message": "工具不存在: not_exist_tool"
    }
    }
    
  6. 关闭流程
    C → S:发起关闭请求
    {
    "jsonrpc": "2.0",
    "id": 5,
    "method": "shutdown",
    "params": {}
    }
    
    S → C:确认关闭
    {
    "jsonrpc": "2.0",
    "id": 5,
    "result": {}
    }
    
    C → S:退出通知(无响应)
    {
    "jsonrpc": "2.0",
    "method": "notifications/exit",
    "params": {}
    }
    
    收到后服务端可安全退出进程。

如何写
mcp的出现让大模型有了直接调本地处理工具或第三方服务的能力
服务本地版(Java)
Java 原生手写 MCP(理解底层原理)
无需任何 MCP 框架,仅用 Jackson 处理 JSON,基于 stdio 实现完整协议,适合理解 MCP 底层逻辑。

  1. 依赖(Maven)
    仅需 JSON 处理库:
    <dependencies>
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.2</version>
    </dependency>
    </dependencies>
    
  2. 完整可运行代码
    运行
    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>
  1. 业务工具类
    只需用 @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();
    }
}

框架会自动完成协议握手、工具列表生成、参数校验与解析、异常处理等所有底层工作,开发者只需专注业务逻辑。
五、运行与接入测试

  1. 本地手动验证
    运行主程序后,在控制台输入初始化请求 JSON,可验证服务是否正常响应:
    {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test-client","version":"1.0.0"}}}
    
  2. 接入 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 个要素:

  1. 工具描述(description)
    这是影响选择的第一要素。
    ❌ 反面例子:“description”: “处理数据”(太模糊,模型无法判断适用场景)
    ✅ 正面例子:“description”: “查询指定服务器的CPU、内存、磁盘实时使用率,输入服务器IP地址”(场景、能力、入参都讲清楚)
    描述越具体、越贴近真实使用场景,模型越容易在对应问题下选中它。
  2. 工具名称(name)
    名称是语义匹配的强信号,模型会优先通过名称做初步匹配。
    建议使用「动词 + 名词」的命名方式,比如 query_server_metrics、generate_excel_report
    避免使用缩写、黑话、无意义的命名
  3. 参数 Schema 与参数描述
    inputSchema 里每个字段的 description 也会影响模型判断,同时决定了模型能不能正确提取参数。
    每个参数都要写清楚含义、格式要求(比如 “格式为 YYYY-MM-DD”、“单位为秒”)
    必填项和可选项要明确区分
    枚举值尽量列全,减少模型参数填充错误
  4. 工具排序与上下文位置
    客户端传给模型的工具列表是有顺序的,排在前面的工具被选中的概率会略高。
    多数客户端按 MCP 配置顺序 + 工具在 tools/list 中的返回顺序排列
    做过语义预筛选的客户端,会把匹配度高的工具排在前面
    常见的补充机制
  5. 用户手动干预
    几乎所有 MCP 客户端都支持人工干预选择:
    用户可以手动禁用某个 MCP 服务或单个工具,禁用后模型完全看不到
    部分客户端支持「@工具」手动指定调用,强制模型使用某个工具
    高危工具调用前会弹窗二次确认,用户同意后才会真正执行
  6. 多轮对话中的工具复用
    在同一轮对话中,模型会记住已经调用过的工具,后续相关问题会优先复用已经用过的工具,而不是重新选一个新的。
  7. 错误重试与工具替换
    如果调用某个工具失败、返回结果不符合预期,模型可能会:
    调整参数重试同一个工具
    换另一个功能相似的工具尝试
    放弃调用工具,直接基于自身知识回答
    四、优化建议:让你的 MCP 工具更容易被选中
    如果你在开发 MCP 服务,可以按以下原则优化,提升工具被正确调用的概率:
    工具名直白:用动宾结构,见名知意
    描述写场景:不要只写 “做什么”,还要写 “适合什么时候用”
    参数讲清楚:每个参数的含义、格式、取值范围都写完整
    单一职责:一个工具只做一件事,不要做全能工具,否则描述会模糊,匹配度下降
    避免重名:和常见工具(比如文件读写、搜索)区分开,防止被系统工具覆盖
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐