一、介绍

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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐