MCP实践:MCP server 开发保姆教程(附全部代码)

4,536 阅读5分钟

1、概述

MCP的出现为AI的落地发展摁下了加速键,我们通过MCP可以解耦工具和大模型之间的强耦合关系。使一个工具服务可以供不同厂商的大模型调用。加速了工具服务组件化发展。

具体介绍可以看 模型上下文协议(MCP):AI 代理与真实世界交互的桥梁

相关文章:

这篇文章介绍MCP-server的开发及相关调试及一些注意点。文章最后附有全部代码

2、MCP server开发

MCP server 开发有两种模式,一种是stdio模式,另一种是sse模式。stdio相当于是依赖包加载到本地进行运行,

2.1、依赖相关

开发mcp对环境有要求,jdk要求17+,SpringBoot要求【3.4.0+】

MCP-server开发有两个依赖包,底层的技术一个是依赖webmvc,另一个是依赖webflux。webmvc是基于servlet的阻塞式模型,webflux是一个异步非阻塞式的 Web 框架,它能够充分利用多核 CPU 的硬件资源去处理大量的并发请求。

具体两个依赖如下(目前最新版本【1.0.0-M7】):

<!--  webmvc-mcp -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
    <version>1.0.0-M7</version>
</dependency>


<!-- webflux-mcp -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
    <version>1.0.0-M7</version>
</dependency>

我们选其中之一【webmvc】进行演示讲解。

2.2、代码开发

大家可以看下目录结构,结构比较简单。主要核心是【OpenMeteoService】和【MCPSpringAiRegisterConfiguration】 image.png

服务开发

我们使用open-meteo服务,【open-meteo】是一个开源免费的天气api服务。

这里我们开发两个MCP工具

根据经纬度查询天气 image.png

根据经纬度查询空气质量 image.png

配置开发

初始化restTemplate image.png

Tools服务注册 image.png

application.yml配置

目前使用的都是默认配置,只添加了一个name和version,具体配置可参考官网 image.png

目前【sse-endpoint】配置不起作用,原因是依赖初始化时使用默认值,因此如果要使用自定义,需要重写初始化。 image.png

解决方案: 创建配置,重写WebMvcSseServerTransportProvider初始化 image.png

3、接口验证

我们使用开源组件inspector进行验证mcp服务

运行inspector需要安装node、npm、npx环境

执行【npx @modelcontextprotocol/inspector】命令便可安装运行 启动服务地址为【http://127.0.0.1:6274】

image.png

具体调试信息如下: image.png

添加【sse-endpoint】配置后如下: image.png image.png

4、总结

目前只是运行了一个简单的demo,离服务化还是较远。但提出了工具组件化的概念。

对于MCP,还有好多需要完善,比如鉴权、相关依赖完善、各个服务提供商的数据是否完备等。但是这一技术理念还是值得我们现在去理解去学习。

5、代码

代码结构如下:

image.png

BeanConfiguration

package com.young.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author 刘子洋
 * @date 2025年05月13日 下午3:22
 * @description:初始化组件bean
 */
@Configuration
public class BeanConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

MCPSpringAiRegisterConfiguration

package com.young.server.config;

import com.young.server.mcp.springAi.OpenMeteoService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 刘子洋
 * @date 2025年05月13日 下午3:18
 * @description:MCP 服务注册
 */
@Configuration
public class MCPSpringAiRegisterConfiguration {

    /**
     * 创建并配置一个天气工具回调提供者
     * 该方法通过接收一个OpenMeteoService实例来构建一个MethodToolCallbackProvider对象
     * 主要用于在调用OpenMeteoService的方法时提供回调功能
     *
     * @param openMete OpenMeteoService的实例,用于获取天气信息
     * @return 返回一个配置好的MethodToolCallbackProvider对象
     */
    @Bean
    public ToolCallbackProvider weatherTools(OpenMeteoService openMete){
        return MethodToolCallbackProvider.builder()
                .toolObjects(openMete)
                .build();
    }

}

MyMcpServerConfig

package com.young.server.config;//package com.young.server.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

/**
 * @author 刘子洋
 * @date 2025年05月14日 上午10:53
 * @description:重写 WebMvcSseServerTransportProvider 初始化
 */
@Configuration
public class MyMcpServerConfig {

    @Bean
    @Primary
    public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(
            ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) {
        ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
        return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getSseMessageEndpoint(),
                serverProperties.getSseEndpoint());
    }

    @Bean
    public RouterFunction<ServerResponse> mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) {
        return transportProvider.getRouterFunction();
    }

}

OpenMeteoService

package com.young.server.mcp.springAi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @author 刘子洋
 * @date 2025年05月13日 下午2:53
 * @description:空气服务类
 */
@Service
@Slf4j
public class OpenMeteoService {

    private final RestTemplate restTemplate;

    private static final String WEATHER_TEMPLATE = "当前位置(纬度:%s,经度:%s)的天气信息:\n %s";

    public OpenMeteoService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    /**
     * 根据给定的经纬度获取天气预报
     * 此方法使用RestTemplate调用外部天气API,获取JSON格式的天气信息,并将其格式化为字符串返回
     *
     * @param latitude  纬度,表示地理位置的南北位置
     * @param longitude 经度,表示地理位置的东西位置
     * @return 格式化后的天气预报信息字符串
     */
    @Tool(description = "根据给定的经纬度获取天气预报")
    public String getWeatherForecastByLocation(
        @ToolParam(description = "经纬度,例如:39.9042") String latitude,
        @ToolParam(description = "经纬度,例如:116.4074") String longitude
    ) {
        String url = "https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m&timezone=auto";
        String response = restTemplate.getForObject(url, String.class, latitude, longitude);
        log.info("response: {}", response);
        return String.format(WEATHER_TEMPLATE, latitude, longitude, response);
    }

    /**
     * 根据给定的经纬度获取空气质量信息
     *
     * @param latitude  纬度,表示地理位置的南北位置
     * @param longitude 经度,表示地理位置的东西位置
     * @return 空气质量信息字符串
     */
    @Tool(description = "根据经纬度获取空气质量信息")
    public String getAirQualityByLocation(
        @ToolParam(description = "经纬度,例如:39.9042") String latitude,
        @ToolParam(description = "经纬度,例如:116.4074") String longitude
    ) {
        // 模拟数据,实际应用中应调用真实API
        return "当前位置(纬度:" + latitude + ",经度:" + longitude + ")的空气质量:\n" +
                "- PM2.5: 15 μg/m³ (优)\n" +
                "- PM10: 28 μg/m³ (良)\n" +
                "- 空气质量指数(AQI): 42 (优)\n" +
                "- 主要污染物: 无";
    }
}

maven(有些组件没有在maven中心仓库,因此自定义加入了一些仓库)

<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 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.young</groupId>
        <artifactId>my-agi</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>mcp-server</artifactId>
    <packaging>war</packaging>
    <name>mcp-server Maven Webapp</name>
    <url>http://maven.apache.org</url>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
            <version>1.0.0-M7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.4.0</version>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <id>central-portal-snapshots</id>
            <name>Central Portal Snapshots</name>
            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>