让AI再次伟大-MCP-Server开发指南

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理、AI应用
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2025计划中:逆水行舟,不进则退
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

MCP 是什么

如何去开发属于自己的MCP服务,并使用cursoor进行本地配置,那么MCP是什么?首先我们可以进入到MCP的官方网站

https://mcp-docs.cn/introduction

全称是Model Context Protocol,是连接大模型 与 外部资源工具的标准化接口服务,说的通俗一些呢,MCP就是标准化的function call,只不过这个function call是属于大模型与外部资源工具之间的交互。模型本身呢只是对数据进行计算 和 处理的。

模型本身是不具备获取外部资源 和 工具的能力,而MCP为大模型提供的 具备外部资源 和 工具的能力,并且MCP的诞生可以让大模型自行调用这些资源 和 工具,比如AI模型没有能力使用我们本地数据库的数据,然后使用特定结构的sql查询获取数据再生成图表的这个过程。

那么使用MCP来实现这个功能,一个MCP负责通过SQL查询获取数据,然后另一个MCP负责生成图表,比如生成图片格式或者HTML格式,这些都是可以让MCP提供工具,让大模型去使用的。

那么本篇文章会依据官方文档从零开始使用MCP开发一个天气查询服务。

我们可以继续看下mcp的整体运行流程。

上图是mcp的客户端 和 三个mcp服务在一台服务器上部署,前两个mcp服务对接的是本地的数据库数据,第三个mcp服务通过web api去请求了互联网上的外部资源数据,mcp客户端可以获知这三个mcp server的具体能力,然后再把这些能力交给大模型,那么大模型就会知道这三个mcp server的具体能力,那么在我们使用大模型的时候,大模型就可以自行判断是否要使用相关mcp的tools,也就是大模型的工具,接下来ai模型就可以通过某个mcp server提供的工具获取我哦们本地数据库的数据 或者 外部资源的数据,对相关数据进行计算 和 推理。

关于mcp server的开发,根据官方文档的描述,核心就是我们去创建一个天气查询的mcp服务,主要提供几个tools,然后我们把这几个tools连接到我们的mcp主机上,也就是带有client的大模型中,就可以正常使用了。

MCP 核心概念

接下来说一下mcp的核心概念,其实他可以提供三种主要类型的功能。

第一种resource,客户端可以读取类似文件的数据,如api响应 或 文件内容,也就是说,我们自己开发好的后端api 或 第三方的api接口 或者第三方文件可以通过resource来进行读取。区别在于传统api使用时或者我们程序主动调用 或 处理数据的过程,resource将数据源的注册为标准化URI资源,允许模型通过统一接口直接访问。无需关心底层实现细节。也就是说我们有一个url,这个url可以直接接入数据库某个表的数据,然后模型就可以直接通过这个url来获取这个表的数据,并且不会像使用tools那样需要我们进行确认,这样的做法,丰富了模型的上下文信息。对于resource,标准规范是只对数据进行读取,不能进行写入。

第二个 tools可以让模型进行相关的工具函数的使用,但是他需要用户批准,也就是说我们开发的mcp服务,可以提供很多函数,这些函数可以让模型自行去使用,比如说我们获取了一个获取天气预报的函数,那么模型就可以自己调用这个函数来获取天气信息,那么类比一下,就好比我们传统开发后端api一样,我们自己开发了一个获取天气预报的api接口,然后我们自己去使用这个api来获取天气预报信息,只不过这个过程是把我们自己替换成了模型而已。

第三个 prompts提示词,帮助用户完成特定任务的预写模板,其实就是为tools工具提供了一个提示词,让模型可以根据提示词模板进行回答,就比如说我通过天气预报函数 获取今天天气的同时,我还想让模型给我出门穿衣服的建议,那么我就可以为这个天气函数提供一个提示词,让模型可以根据提示词模板来进行回答。

设置环境

接下来使用python来开发一下这个 server。版本必须在3.10以上、Python MCP SDK 1.2.0 或者更高版本

根据官方文档,简单设置下环境:

curl -LsSf https://astral.sh/uv/install.sh | sh

之后请确保重启终端,以确保 uv 命令被正确识别。

现在,让我们创建并设置我们的项目:

# 为我们的项目创建一个新目录
uv init weather
cd weather
# 创建虚拟环境并激活它,我们创建一个目录 weather_env 来存放虚拟环境
uv venv weather_env
source ./weather_env/Scripts/activate
# 测试 python 是否是指定的当前虚拟环境中的python
which python
# 安装 http 依赖用于调用外部API 和 mcp cli的相关命令行工具,
# --active 让 uv 使用当前激活的环境安装相关依赖
uv add "mcp[cli]" httpx --active
# 创建我们的服务器文件
touch weather.py

其中mcp clie提供了对mcp server进行本地调试的能力,而httpx主要是用于对外部的天气api接口进行请求,因为我们需要http请求的方式。

当前置的准备工具就位后,就可以在 weather.py 文件里开发了。。。

正式构建应用

接下来就开始正式开发了

导入包

from typing import Any

# 用于发送 HTTP 请求
import httpx
# 用于创建 MCP 服务
from mcp.server.fastmcp import FastMCP

# 初始化一个MCP服务实例,服务名称就是weather,这将作为 MCP 客户端或大模型识别服务的标识
mcp = FastMCP("weather")

# 定义OpenWeatherMap API 的基础URL
OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5"

# API 密钥
OPENWEATHER_API_KEY = "××"
# API 调用的身份识别
USER_AGENT = "weather-app/1.0"

关于openWeather没有注册的需要注册下

https://openweathermap.org/api

这里面有一个接口是根据城市名称获取天气信息的api接口。使用之前需要一个api key需要前置自己申请好!!!

辅助函数

# 核心工具函数:负责向OpenWeatherMap API 发送请求并处理响应
async def make_weather_request(url: str) -> dict[str, Any] | None:
    """向OpenWeatherMap API 发送请求并处理响应,包括适当的错误处理。

    Args:
        url (str): 完整的OpenWeatherMap API 请求URL
        
    Returns:
        dict[str, Any]
        None: 如果请求成功,返回响应的JSON数据,否则返回None
    """
    # 设置请求头,包含User-Agent标识
    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/json",
        "Content-Type": "application/json",
    }
    # 创建异步Http客户端会话
    async with httpx.AsyncClient() as client:
        try:
            # 发送GET请求,并设置请求头和超时
            response = await client.get(url, headers=headers, timeout=30.0)
            # 检查HTTP状态,非2XX状态码会抛出异常
            response.raise_for_status()
            # 解析JSON响应数据并返回
            return response.json()
        except Exception as e:
            print(f"错误 {e}")
            return None

调用的核心工具函数就以及开发完了,它主要是对天气API进行请求,并获取api返回的数据。

# 辅助函数:将当前JSON格式的天气数据转换为可读的文本格式
def format_weather_data(data: dict, units: str = "metric") -> str:
    """将JSON格式的天气数据转换为可读的文本字符串格式
    
    Args:
        data (dict): JSON格式的天气数据
        
    Returns:
        str: 格式化的天气信息文本
    """
    # 检查data是否为空
    if not data:
        return "无法获取天气数据"
    
    # 从 API 响应中提取关键天气信息
    # 获取国家代码
    country_code = data.get("sys", {}).get("country", "未知")
    # 获取城市名称
    city_name = data.get("name", "未知")
    # 获取天气描述
    weather_desc = data.get("weather", [{}])[0].get("description", "未知")
    # 获取当前温度(摄氏度)
    temp = data.get("main", {}).get("temp", "未知")
    # 获取体感温度(摄氏度)
    feels_like = data.get("main", {}).get("feels_like", "未知")
    # 获取湿度(百分比)
    humidity = data.get("main", {}).get("humidity", "未知")
    # 获取风速(m/s)
    wind_speed = data.get("wind", {}).get("speed", "未知")

    # 根据 units 参数确定温度和风速的单位
    temp_unit = "°C" if units == "metric" else "°F"
    wind_speed_unit = "m/s" if units == "metric" else "mph"

    # 构建格式化的天气信息字符串
    return f"""
    城市: {city_name}
    国家: {country_code}
    天气: {weather_desc}
    温度: {temp} {temp_unit}
    体感温度: {feels_like} {temp_unit}
    湿度: {humidity}%
    风速: {wind_speed} {wind_speed_unit}
    """

想知道具体的返回格式只需要 curl一下即可,就能拿到具体的返回值,然后直接封装解析即可。

工具开发

基础版本

# @mcp.tool() 是一个装饰器,用于将函数注册为 MCP 工具,允许大模型进行调用
# @mcp.tool(name="get_weather", description="获取指定城市的当前天气信息, 如果用户使用中文询问某城市天气,你必须将城市名转换为相应的英文名称再调用API。")
@mcp.tool()
# MCP 工具1:根据城市获取当前的天气信息,由大模型调用这个函数,函数参数由大模型传入
async def get_weather(city: str, country_code: str = None, state_code: str = None, units: str = "metric", lang: str = "zh_cn") -> str:
    """获取指定城市的当前天气信息,如果用户使用中文询问某城市天气,
    你必须将城市名转换为相应的英文名称再调用API。

    Args:
        city: 城市名称 (例如:Beijing, Shanghai, New York)
        country_code: 国家代码(可选,例如:CN, US, JP)
        state_code: 州/省代码(可选,例如:BJ, SH, NY)
        units: 测量单位(可选,默认metric)
        lang: 语言(可选,默认zh_cn)
    
    Returns:
        str: 格式化的当前天气信息文本
    """
    # 构建位置查询参数,支持城市名称、州代码和国家代码组合
    location_query = city
    if state_code and country_code:
        # 格式:城市,州代码,国家代码
        location_query = f"{city},{state_code},{country_code}"
    if country_code:
        # 格式:城市,国家代码
        location_query = f"{city},{country_code}"
    
    # 构建API请求URL,参数包含位置查询、API密钥、测量单位和语言
    url = f"{OPENWEATHER_API_BASE}/weather?q={location_query}&appid={OPENWEATHER_API_KEY}&units={units}&lang={lang}"
    
    # 发送请求并获取响应数据
    data = await make_weather_request(url)

    # 检查 API 响应中的错误代码
    if "cod" in data and data["cod"] != 200:
        return f"获取天气信息失败,错误:{data.get('message', '未知错误')}"
    
    # 使用辅助函数格式化天气信息并返回,传入data和units参数
    return format_weather_data(data, units)

其实上述中的描述是最关键的,就按照这个格式来就好。

"""获取指定城市的当前天气信息,如果用户使用中文询问某城市天气,
你必须将城市名转换为相应的英文名称再调用API。

    Args:
        city: 城市名称 (例如:Beijing, Shanghai, New York)
        country_code: 国家代码(可选,例如:CN, US, JP)
        state_code: 州/省代码(可选,例如:BJ, SH, NY)
        units: 测量单位(可选,默认metric)
        lang: 语言(可选,默认zh_cn)
    
    Returns:
        str: 格式化的当前天气信息文本
    """

描述的作用是 大模型最终会根据我们函数中的描述去选择它最终要去调用哪个tool工具

# 程序入口点
if __name__ == "__main__":
    # 初始化并运行 MCP 服务,使用标准输入输出作为传输方式
    mcp.run(transport='stdio')

我们目前是mcp服务会部署在本地,会使用cursor这个mcp客户端去使用。

接下来我们要怎么测试这个服务呢?

测试运行

mcp dev weather.py

替换路径即可,点击connect即可。

此时我们就连接成功了,然后在右侧输入测试即可。

基本版本2

# MCP 工具2:提供5天天气预报的查询功能,传入城市名称、国家代码(可选)、州代码(可选)、测量单位(可选)和语言(可选)
@mcp.tool()
async def get_forecast(city: str, country_code: str = None, state_code: str = None, units: str = "metric", lang: str = "zh_cn") -> str:
    """获取指定城市的5天天气预报信息,如果用户使用中文询问某城市5天天气预报,
    你必须将城市名转换为相应的英文名称再调用API。
    
    Args:
        city: 城市名称 (例如:Beijing, Shanghai, New York)
        country_code: 国家代码(可选,例如:CN, US, JP)
        state_code: 州/省代码(可选,例如:BJ, SH, NY)
        units: 测量单位(可选,默认metric)
        lang: 语言(可选,默认zh_cn)
    
    Returns:
        str: 格式化的5天天气预报信息文本
    """
    # 构建位置查询参数,支持城市名称、州代码和国家代码组合
    location_query = city
    if state_code and country_code:
        # 格式:城市,州代码,国家代码
        location_query = f"{city},{state_code},{country_code}"
    if country_code:
        # 格式:城市,国家代码
        location_query = f"{city},{country_code}"
    
    # 构建API请求URL,参数包含位置查询、API密钥、测量单位和语言
    url = f"{OPENWEATHER_API_BASE}/forecast?q={location_query}&appid={OPENWEATHER_API_KEY}&units={units}&lang={lang}"

    # 发送请求并获取响应数据
    data = await make_weather_request(url)

    # 检查data是否为空
    if not data:
        return "无法获取天气数据"
    
    # 检查 API 响应中的错误代码
    if "cod" in data and data["cod"] != "200":
        return f"获取5天天气预报信息失败,错误:{data.get('message', '未知错误')}"
    
    # 提取5天天气预报信息
    forecast_list = data.get("list", [])
    # 如果天气数组为空
    if not forecast_list:
        return "无法获取5天天气预报信息"
    
    # 构建格式化的5天天气预报信息数组
    forecast_data = []
    # 遍历5天天气预报信息,并提取日期和时间
    for forecast in forecast_list:
        # 提取日期和时间
        date_time = forecast.get("dt_txt", "")
        # 提取天气描述
        weather_desc = forecast.get("weather", [{}])[0].get("description", "未知")
        # 提取温度
        temp = forecast.get("main", {}).get("temp", "未知")
        # 提取湿度
        humidity = forecast.get("main", {}).get("humidity", "未知")
        # 提取风速
        wind_speed = forecast.get("wind", {}).get("speed", "未知")
        # 根据 units 参数确定温度和风速的单位
        temp_unit = "°C" if units == "metric" else "°F"
        wind_speed_unit = "m/s" if units == "metric" else "mph"
        # 构建格式化的5天天气预报信息字符串
        forecast_str = f"""
        日期: {date_time}
        天气: {weather_desc}
        温度: {temp} {temp_unit}
        湿度: {humidity}%
        风速: {wind_speed} {wind_speed_unit}
        """
        # 提取天气预报数据
        forecast_data.append(forecast_str)
    # 返回格式化的5天天气预报信息数组,使用分隔符链接所有天气预报
    return "\n---\n".join(forecast_data)

测试同上。

cursor运行

接下来给cursor配置mcp server

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
        "run",
        "weather.py"
      ]
    }
  }
}

含有模板的 版本

# 这个就是告诉大模型你应该使用什么文本形式给我答案
# mcp.prompt() 是一个装饰器,用于将函数注册为 MCP 提示,允许大模型进行调用
@mcp.prompt()
# 那么这个函数接收的数据是由大模型传入的,也就是说它把天气数据传入进来。
async def weather_prompt(city: str, weather_desc: str, temp: float, humidity: float, wind_speed: float, temp_unit: str, speed_unit: str) -> str:
    """用于生成天气报告的提示模板

    Args:
        city: 城市名称
        weather_desc: 天气描述
        temp: 温度
        humidity: 湿度
        wind_speed: 风速
        temp_unit: 温度单位
        speed_unit: 风速单位
    """
    # 构建天气报告的格式化文本
    return f"""请你作为专业的气象播报员,根据以下天气数据生成一份简介、易懂的天气报告:

    城市: {city}
    天气: {weather_desc}
    温度: {temp} {temp_unit}
    湿度: {humidity}%
    风速: {wind_speed} {speed_unit}

    报告内容包括:
    1.今日天气概况
    2.根据温度和湿度分析体感情况
    3.根据天气状况提供穿衣建议
    4.适合的户外活动推荐

    最后请使用自然、专业的语言,避免过于技术性的术语,要贴近生活。
    """
# mcp工具3:获取指定城市的天气信息并提供报告模板
@mcp.tool()
async def weather_report(city: str, country_code: str = None, state_code: str = None, units: str = "metric", lang: str = "zh_cn") -> dict:
    """获取指定城市的天气信息并提供报告模板

    Args:
        city: 城市名称
        country_code: 国家代码(可选,例如:CN, US, JP)
        state_code: 州/省代码(可选,例如:BJ, SH, NY)
        units: 测量单位(可选,默认metric)
        lang: 语言(可选,默认zh_cn)
    
    Returns:
        dict: 包含天气数据和提示模板信息的字典
    """
    # 获取原始天气信息
    weather_result = await get_weather(city, country_code, state_code, units, lang)

    # 解析天气文本获取关键信息
    import re
    # 使用正则表达式提取天气信息
    city_match = re.search(r'城市: (.*?)(?:\n|$)', weather_result)
    weather_match = re.search(r'天气: (.*?)(?:\n|$)', weather_result)
    temp_match = re.search(r'温度: ([\d.]+)(.*?)(?:\n|$)', weather_result)
    humidity_match = re.search(r'湿度: ([\d.]+)%', weather_result)
    wind_match = re.search(r'风速: ([\d.]+) (.*?)(?:\n|$)', weather_result)

    # 提取数据值,如果无法提取则使用默认值
    city_name = city_match.group(1) if city_match else city
    weather_desc = weather_match.group(1) if weather_match else "未知"
    temp_value = float(temp_match.group(1)) if temp_match else 0.0
    temp_unit = temp_match.group(2) if temp_match and len(temp_match.groups()) > 1 else "°C"
    humidity_value = int(float(humidity_match.group(1))) if humidity_match else 0
    wind_speed = float(wind_match.group(1)) if wind_match else 0.0
    speed_unit = wind_match.group(2) if wind_match and len(wind_match.groups()) > 1 else "m/s"

    # 构建返回结果,包含三个部分:原始的数据,模板名称,模板参数
    
    # 大模型调用这个工具会收到包含原始数据,模板名称,模板参数的结构化返回结果
    
    # 大模型识别到prompt_template字段指向 weather_report 这个函数模板,
    # 会调用这个函数,并传入模板参数
    
    # 大模型自动用 template_args 中的值填充 weather_report 模板中的对应参数。
    
    # 填充后的模板会成为大模型自己的 “思考提示”,然后大模型会根据这个提示,生成最终的回答。
    
    # 这就像我们去餐厅点餐:
    
    # mcp.prompt()的定义就相当于菜单上的标准食谱
    
    # weather_prompt 函数相当于收集食材
    
    # 当模型调用这个weather_report工具时,
    # 模型接收到了食谱(prompt_template)和食材(template_args)
    # 模型就相当于厨师,按照食谱(提示模板),使用食材(模板参数)烹饪菜品(回答)
    return {
        "raw_data": weather_result,
        "prompt_template": "weather_prompt",
        "template_args": {
            "city": city_name,
            "weather_desc": weather_desc,
            "temp": temp_value,
            "temp_unit": temp_unit,
            "humidity": humidity_value,
            "wind_speed": wind_speed,
            "speed_unit": speed_unit,
        }
    }

测试

继续使用cursor,项目重新启动一下。

<think>好的,用户的问题是关于Spring AI Starter MCP Server WebMVC的配置和使用方法。我需要先回忆一下之前提到的相关信息,特别是引用[1]中提到的Spring AI MCP通过Spring Boot集成扩展了MCP Java SDK,提供客户端和服务器启动器。用户现在想具体了解这个项目的配置和使用步骤,可能需要分步骤指导。 首先,我应该确认项目的依赖配置,比如Maven的pom.xml或者Gradle的build.gradle。然后,可能需要引导用户如何初始化项目,比如使用Spring Initializr添加依赖。接下来是配置文件的设置,比如application.properties中的服务器端口和其他参数。然后,可能需要创建控制器或者服务类,使用Spring MVC的注解来处理请求。另外,可能需要提到如何启动应用并进行测试,比如通过curl命令或者Postman发送请求。 同时,用户可能遇到的问题,比如依赖未正确引入,配置项遗漏,或者端口冲突,也需要在注意事项里提到。此外,参考之前的引用内容,确保引用的架构部分被正确提及。最后,生成的相关问题需要围绕配置扩展、客户端使用、日志调试和安全性展开,帮助用户进一步探索。需要检查是否所有步骤都清晰,并符合用户的技术水平,避免使用过于专业的术语,但保持准确性。</think>### Spring AI Starter MCP Server WebMVC 配置与使用指南 #### 一、项目初始化配置 1. **添加依赖** ```xml <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>${spring.ai.version}</version> </dependency> ``` 建议通过Spring Initializr创建项目时勾选"Spring Web"和"Spring AI MCP Server"模块[^1] 2. **配置文件示例** (`application.properties`) ```properties server.port=8080 spring.ai.mcp.endpoint=/ai/api spring.ai.mcp.model-provider=openai spring.ai.mcp.api-key=${YOUR_API_KEY} ``` #### 二、核心功能实现 1. **创建控制器** ```java @RestController @RequestMapping("/ai/api") public class AiController { @PostMapping("/query") public ResponseEntity<AiResponse> handleQuery( @RequestBody AiRequest request) { // 调用MCP SDK处理请求 return new ResponseEntity<>(process(request), HttpStatus.OK); } } ``` 2. **服务配置类** ```java @Configuration @EnableMcpServer public class McpConfig { @Bean public ModelProvider modelProvider() { return new OpenAIModelProvider(); } } ``` #### 三、运行与测试 1. **启动类配置** ```java @SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } } ``` 2. **测试请求示例** ```bash curl -X POST http://localhost:8080/ai/api/query \ -H "Content-Type: application/json" \ -d '{"prompt":"解释量子计算原理"}' ``` #### 四、注意事项 1. API密钥需通过环境变量或配置中心管理 2. 默认使用OpenAI模型,可扩展其他AI服务商 3. 建议启用Spring Security进行接口保护
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃芝士的土豆倪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值