从零到一,3万字详解MCP工具【开发、部署上线、维护】全流程!

本期内容全程手撕代码、逐帧剖析💥带你“从零到一”【开发】MCP工具,再将自己开发的MCP工具【部署上线】,并进行本地的MCP工具【维护】!


一、MCP技术快速入门回顾

1.MCP服务器Server合集

图片

图片

  • MCP官方服务器合集:https://github.com/modelcontextprotocol/servers

  • MCP Github热门导航:https://github.com/punkpeye/awesome-mcp-servers

图片

图片

图片

  • Smithery:https://smithery.ai/

  • MCP导航:https://mcp.so/

  • 阿里云百炼:https://bailian.console.aliyun.com/?tab=mcp

2. MCP热门客户端Client

        除了能在命令行中创建MCP客户端外,还支持各类客户端的调用:https://modelcontextprotocol.io/clients

图片

3.MCP工具快速接入示例

以下是一个将高德地图导航MCP(服务器)接入Cherry Studio(客户端)的示例:

  • CherryStudio主页:https://github.com/CherryHQ/cherry-studio

图片

  • CherryStudio文档页:https://docs.cherry-ai.com/cherry-studio/download

图片

图片

图片

下载完即可进入对话页面:

图片

然后我们可以将模型切换为DeepSeek官方的模型API:

图片

然后开启:

图片

并尝试进行使用:

图片

图片

同时,为了能顺利调用MCP工具,我们还需要安装uv和bun文件:

图片

这里推荐最快速的方法是直接从网盘中进行下载:

图片

 然后在C:\Users{用户名}下创建.cherrystudio\bin目录,并将上面三个.exe文件移入即可。

图片

其他操作系统配置详见:https://docs.cherry-ai.com/advanced-basic/mcp/install

  • Cherry Studio接入MCP流程

        接下来尝试接入filesystem MCP工具:https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem 。

需要在编辑MCP配置页面输入如下内容:

{
  "mcpServers": {
    "filesystem": {
      "isActive": true,
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:/Users/Administrator/Desktop/最新流量视频/MCP体验课/MCPTest"
      ],
      "name": "filesystem"
    }
  }
}

图片

然后点击开启:

图片

然后在对话中开启MCP工具,这里可选一个或者多个工具:

图片

图片

图片

同时再尝试接入fetch MCP工具,https://github.com/modelcontextprotocol/servers/tree/main/src/fetch:

图片

Fetch MCP 服务器是一个遵循模型上下文协议(Model Context Protocol,MCP)的服务器工具,旨在为大型语言模型(LLMs)提供从互联网检索和处理网页内容的能力。通过将网页的 HTML 内容转换为 Markdown 格式,Fetch MCP 使得 LLMs 能够更高效地理解和利用网页信息。

主要功能:

  • 网页内容获取与转换:Fetch MCP 提供了 fetch 工具,可从指定的 URL 获取网页内容,并将其提取为 Markdown 格式,方便 LLMs 消化和处理。

  • 支持多种内容格式:除了 Markdown,Fetch MCP 还支持获取网页的 HTML、JSON 和纯文本格式,满足不同应用场景的需求。

  • 内容截取与分页:通过 start_index 参数,用户可以指定从网页内容的特定位置开始提取,允许模型分段读取网页,直到找到所需信息。

同样我们需要在MCP配置页面写入如下内容

"fetch": {
    "command": "uvx",
    "args": ["mcp-server-fetch"]
  }

图片

然后开启工具:

图片

并尝试进行调用:

图片


全套代码、相关软件和参考资料已上线至赋范大模型技术社区⬇️。

图片


二、MCP服务器标准调用流程

1.MCP技术生态介绍

MCP基础技术生态结构如下所示。

图片

1.1 MCP通信协议介绍

        MCP(Model Context Protocol)是一种为了统一大规模模型和工具间通信而设计的协议,它定义了消息格式和通信方式。MCP 协议支持多种传输机制,其中包括 stdio、Server-Sent Events(SSE) 和 Streamable HTTP。每种通信方法在不同的应用场景中具有不同的优劣势,适用于不同的需求。

1.1.1 Stdio 传输(Standard Input/Output)

        stdio 传输方式是最简单的通信方式,通常在本地工具之间进行消息传递时使用。它利用标准输入输出(stdin/stdout)作为数据传输通道,适用于本地进程间的交互。

  • 工作方式:客户端和服务器通过标准输入输出流(stdin/stdout)进行通信。客户端向服务器发送命令和数据,服务器执行并通过标准输出返回结果。

  • 应用场景:适用于本地开发、命令行工具、调试环境,或者模型和工具服务在同一进程内运行的情况。

1.1.2 Server-Sent Events(SSE)

        SSE 是基于 HTTP 协议的流式传输机制,它允许服务器通过 HTTP 单向推送事件到客户端。SSE 适用于客户端需要接收服务器推送的场景,通常用于实时数据更新。

  • 工作方式:客户端通过 HTTP GET 请求建立与服务器的连接,服务器以流式方式持续向客户端发送数据,客户端通过解析流数据来获取实时信息。

  • 应用场景:适用于需要服务器主动推送数据的场景,如实时聊天、天气预报、新闻更新等。

1.1.3 Streamable HTTP

  • MCP更新公告:https://modelcontextprotocol.io/development/updates

图片

  • Streamable HTTP更新公告:https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx#streamable-http

图片

        Streamable HTTP 是 MCP 协议中新引入的一种传输方式,它基于 HTTP 协议支持双向流式传输。与传统的 HTTP 请求响应模型不同,Streamable HTTP 允许服务器在一个长连接中实时向客户端推送数据,并且可以支持多个请求和响应的流式传输。

        不过需要注意的是,MCP只提供了Streamable HTTP协议层的支持,也就是规范了MCP客户端在使用Streamable HTTP通信时的通信规则,而并没有提供相关的SDK客户端。开发者在开发Streamable HTTP机制下的客户端和服务器时,可以使用比如Python httpx库进行开发。

  • 工作方式:客户端通过 HTTP POST 向服务器发送请求,并可以接收流式响应(如 JSON-RPC 响应或 SSE 流)。当请求数据较多或需要多次交互时,服务器可以通过长连接和分批推送的方式进行数据传输。

  • 应用场景:适用于需要支持高并发、低延迟通信的分布式系统,尤其是跨服务或跨网络的应用。适合高并发的场景,如实时流媒体、在线游戏、金融交易系统等。

1.1.4 MCP 传输方式优劣势对比

特性

Stdio

SSE

Streamable HTTP

通信方向

双向(但仅限本地)

单向(服务器到客户端)

双向(适用于复杂交互)

使用场景

本地进程间通信

实时数据推送,浏览器支持

跨服务、分布式系统、大规模并发支持

支持并发连接数

中等

高(适合大规模并发)

适应性

局限于本地环境

支持浏览器,但单向通信

高灵活性,支持流式数据与请求批处理

实现难度

简单,适合本地调试

简单,但浏览器兼容性和长连接限制

复杂,需处理长连接和流管理

适合的业务类型

本地命令行工具,调试环境

实时推送,新闻、股票等实时更新

高并发、分布式系统,实时交互系统

三种传输方式总结如下:

  • Stdio 传输:适合本地进程之间的简单通信,适合命令行工具或调试阶段,但不支持分布式。

  • SSE 传输:适合实时推送和客户端/浏览器的单向通知,但无法满足双向复杂交互需求。

  • Streamable HTTP 传输:最灵活、最强大的选项,适用于大规模并发、高度交互的分布式应用系统,虽然实现较复杂,但能够处理更复杂的场景。

1.2 MCP SDK介绍

        截至目前,MCP SDK已支持Python、TypeScript、Java、Kotlin和C#等编程语言进行客户端和服务器创建。

图片

2. MCP服务器标准接入流程

2.1 MCP服务器基本接入流程

一个标准的MCP工具接入客户端流程如图所示:

这种通过 JSON 配置文件 来定义服务的启动命令(如 npx、python、node 等),是一个通用且可扩展的方式。只要在配置文件中指定合适的命令、参数和环境变量,MCP 服务就可以根据配置启动,并与其他服务进行交互。例如配置文件定义了一个 filesystem MCP服务:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\username\\Desktop",
        "C:\\Users\\username\\Downloads"
      ]
    }
  }
}
  • mcpServers:根节点,包含所有 MCP 服务的定义。

  • filesystem:服务名称,可以是任意标识符,表示这个 MCP 服务的类型或功能。

  • command:用于启动服务的命令,这里是 npx,表示使用 npx 来执行某个工具或包。

  • args:命令参数列表,具体用于启动该服务的工具或进程。@modelcontextprotocol/server-filesystem 是指定的 MCP 工具包,后面跟着的是路径参数,表示要处理的文件夹或目录。

这种通过修改配置文件的方式具有较强的通用性和灵活性:

  1. 服务类型不限:可以根据不同的服务需求,替换 command 和 args 部分。比如:

    • 对于文件系统服务,可以使用 server-filesystem;

    • 对于其他服务(如数据库、Web 服务器等),也可以使用相应的工具包(如 npx、python、node 等)来启动。

  2. 灵活的路径和环境配置:可以根据需要指定不同的目录或资源路径作为服务的启动参数。

    • 示例中使用的是路径 C:\\Users\\username\\Desktop 和 C:\\Users\\username\\Downloads,这可以是任何需要由 MCP 服务处理的路径。

  3. 支持多服务配置:可以在同一个配置文件中定义多个服务。例如:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\username\\Desktop"
      ]
    },
    "database": {
      "command": "python",
      "args": [
        "server_database.py",
        "--db-path",
        "C:\\Users\\username\\Data"
      ]
    }
  }
}
  1. 这样,你可以同时启动多个不同类型的 MCP 服务,满足多种需求。

  2. 与外部工具集成:通过这种方式,你还可以将 MCP 服务与外部工具结合,比如运行 Python 脚本、Node.js 工具包,甚至其他命令行工具,只需要在配置文件中定义相应的启动命令和参数即可。

需要注意的是,这种配置MCP工具的方法也正是MCP官方说明文档中推荐的配置方法:https://modelcontextprotocol.io/quickstart/user#windows

图片

同时也是目前主流客户端如Cursor、Clien等配置MCP服务器的基本方法:

图片

同时也是目前主流的MCP平台所支持的调用方法:

  • smithery:

图片

  • mcp.so

图片

GitHub MCP官方合集:

图片

总的来说,这种配置方法是 MCP 协议的标准方式,它通过灵活的配置文件使得服务的启动、管理和扩展变得更加简便和通用。无论是启动本地工具服务,还是部署复杂的分布式系统,MCP 都可以通过这样的 JSON 配置文件来灵活应对各种应用场景。

2.2 MCP工具配置文件命令行等价形式

        需要注意的是,在这个 JSON 配置文件中,npx 命令的本质就是 执行一个命令行工具,并且在首次运行时会自动下载和执行所指定的工具或库。如果该工具没有被缓存,npx 会自动从 npm registry 下载并执行它。

具体来说,这个配置文件中的 npx 命令:

{
  "command": "npx",
  "args": [
    "-y",
    "@modelcontextprotocol/server-filesystem",
    "C:\\Users\\username\\Desktop",
    "C:\\Users\\username\\Downloads"
  ]
}
  1. npx:

    • npx 是 Node.js 自带的工具,目的是用来执行命令行工具或 Node.js 包。如果本地没有安装该工具,npx 会临时下载并执行该工具。

  2. @modelcontextprotocol/server-filesystem:

    • 这是一个 npm 包,当你执行这个命令时,npx 会检查本地是否已经安装了该包。

    • 如果本地没有安装该包,npx 会自动从 npm registry 下载这个包并执行。

    • @modelcontextprotocol/server-filesystem 是用来启动一个 MCP 文件系统工具的包,它根据给定的路径来提供文件系统的接口。

  3. -y:

    • 这是 npx 的一个参数,表示自动接受所有的提示(通常是用于跳过安装时的确认提示)。

  4. 路径参数:

    • C:\\Users\\username\\Desktop 和 C:\\Users\\username\\Downloads 是该命令运行时需要的参数,告诉 server-filesystem 工具它应该操作的文件夹路径。

因此,实际运行时会执行如下等价命令:

npx -y @modelcontextprotocol/server-filesystem C:\\Users\\username\\Desktop C:\\Users\\username\\Downloads

而在首次运行时:

  • npx 会从 npm registry 下载 @modelcontextprotocol/server-filesystem 库。

  • 这个包会被下载到一个 临时缓存目录,并执行相关的命令。

  • 运行完成后,npx 会自动删除临时下载的文件,但工具的执行过程会继续。

后续运行时的行为:

  • 如果你再次运行相同的命令,npx 会首先检查本地是否已缓存该包。

  • 如果该工具包已经存在于缓存中,npx 会直接从缓存中提取并执行,而不会再次从网络上下载。

  • 只有当工具包不存在或版本发生变化时,npx 才会重新下载并执行。

也就是说,从表面上来看,我们是填写了MCP工具就能使一些客户端自动连接MCP服务器,实际上是首次运行的时候,MCP客户端会先下载这些MCP服务器,然后开启一个子进程运行这个服务器,从而使得客户端随时可以调用其服务。

        同时需要注意的是这种配置文件设置方法,肯定也是支持离线脚本运行的,我们也可以直接将写好的脚本上传到服务器中,然后指定本地路径进行运行。

3. MCP服务器类型介绍

        根据MCP协议定义,Server可以提供三种类型的标准能力,Resources、Tools、Prompts,每个Server可同时提供者三种类型能力或其中一种。

  • **Resources:**资源,类似于文件数据读取,可以是文件资源或是API响应返回的内容。比如

  • **Tools:**工具,第三方服务、功能函数,通过此可控制LLM可调用哪些函数。

  • **Prompts:**提示词,为用户预先定义好的完成特定任务的模板。

4.MCP开发基础环境搭建

4.1 uv入门介绍

        MCP开发要求借助uv进行虚拟环境创建和依赖管理。uv 是一个Python 依赖管理工具,类似于 pip 和 conda,但它更快、更高效,并且可以更好地管理 Python 虚拟环境和依赖项。它的核心目标是替代 pip、venv 和 pip-tools,提供更好的性能和更低的管理开销。

uv 的特点:

  1. 速度更快:相比 pip,uv 采用 Rust 编写,性能更优。

  2. 支持 PEP 582:无需 virtualenv,可以直接使用 __pypackages__ 进行管理。

  3. 兼容 pip:支持 requirements.txt 和 pyproject.toml 依赖管理。

  4. 替代 venv:提供 uv venv 进行虚拟环境管理,比 venv 更轻量。

  5. 跨平台:支持 Windows、macOS 和 Linux。

4.2 uv安装流程

方法 1:使用 pip 安装(适用于已安装 pip 的系统)

pip install uv

方法 2:使用 curl 直接安装

如果你的系统没有 pip,可以直接运行:

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

这会自动下载 uv 并安装到 /usr/local/bin。

4.3 uv的基本用法介绍

        安装 uv 后,你可以像 pip 一样使用它,但它的语法更简洁,速度也更快。注意,以下为使用语法示例,不用实际运行。

  • 安装 Python 依赖

uv pip install requests

与 pip install requests 类似,但更快。

  • 创建虚拟环境

uv venv myenv

等效于 python -m venv myenv,但更高效。

  • 激活虚拟环境

source myenv/bin/activate  # Linux/macOS
myenv\Scripts\activate     # Windows
  • 安装 requirements.txt

uv pip install -r requirements.txt
  • 直接运行 Python 项目

如果项目中包含 pyproject.toml,你可以直接运行:

uv run python script.py

这等效于:

pip install -r requirements.txt
python script.py

但 uv 速度更快,管理更高效。

接下来我们就按照标准格式尝试先构建一个 MCP 客户端和服务器。

三、按照标准流程搭建MCP客户端与服务器

1.Server搭建流程

        这里我们尝试一个入门级的示例,那就是创建一个天气查询的服务器。通过使用OpenWeather API,创建一个能够实时查询天气的服务器(server),并使用stdio方式进行通信。

图片

测试查询效果

curl -s "https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid='YOUR_API_KEY'&units=metric&lang=zh_cn"

图片

测试无误后,接下来即可进入到创建server的环节中。

3. 天气查询服务器Server创建流程

1.1 服务器依赖安装

由于我们需要使用http请求来查询天气,因此需要在当前虚拟环境中添加如下依赖

uv add mcp httpx

3.2 服务器代码编写

接下来尝试创建服务器代码,此时MCP基本执行流程如下:

图片

对应server服务器代码如下:

import json
import httpx
from typing import Any
from mcp.server.fastmcp import FastMCP

# 初始化 MCP 服务器
mcp = FastMCP("WeatherServer")

# OpenWeather API 配置
OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5/weather"
API_KEY = "YOUR_API_KEY"  # 请替换为你自己的 OpenWeather API Key
USER_AGENT = "weather-app/1.0"

async def fetch_weather(city: str) -> dict[str, Any] | None:
    """
    从 OpenWeather API 获取天气信息。
    :param city: 城市名称(需使用英文,如 Beijing)
    :return: 天气数据字典;若出错返回包含 error 信息的字典
    """
    params = {
        "q": city,
        "appid": API_KEY,
        "units": "metric",
        "lang": "zh_cn"
    }
    headers = {"User-Agent": USER_AGENT}

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(OPENWEATHER_API_BASE, params=params, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()  # 返回字典类型
        except httpx.HTTPStatusError as e:
            return {"error": f"HTTP 错误: {e.response.status_code}"}
        except Exception as e:
            return {"error": f"请求失败: {str(e)}"}

def format_weather(data: dict[str, Any] | str) -> str:
    """
    将天气数据格式化为易读文本。
    :param data: 天气数据(可以是字典或 JSON 字符串)
    :return: 格式化后的天气信息字符串
    """
    # 如果传入的是字符串,则先转换为字典
    if isinstance(data, str):
        try:
            data = json.loads(data)
        except Exception as e:
            return f"无法解析天气数据: {e}"

    # 如果数据中包含错误信息,直接返回错误提示
    if "error" in data:
        return f"⚠️ {data['error']}"

    # 提取数据时做容错处理
    city = data.get("name", "未知")
    country = data.get("sys", {}).get("country", "未知")
    temp = data.get("main", {}).get("temp", "N/A")
    humidity = data.get("main", {}).get("humidity", "N/A")
    wind_speed = data.get("wind", {}).get("speed", "N/A")
    # weather 可能为空列表,因此用 [0] 前先提供默认字典
    weather_list = data.get("weather", [{}])
    description = weather_list[0].get("description", "未知")

    return (
        f"🌍 {city}, {country}\n"
        f"🌡 温度: {temp}°C\n"
        f"💧 湿度: {humidity}%\n"
        f"🌬 风速: {wind_speed} m/s\n"
        f"🌤 天气: {description}\n"
    )

@mcp.tool()
async def query_weather(city: str) -> str:
    """
    输入指定城市的英文名称,返回今日天气查询结果。
    :param city: 城市名称(需使用英文)
    :return: 格式化后的天气信息
    """
    data = await fetch_weather(city)
    return format_weather(data)

if __name__ == "__main__":
    # 以标准 I/O 方式运行 MCP 服务器
    mcp.run(transport='stdio')

图片

代码解释如下:

Part 1. 异步获取天气数据

  • 函数 fetch_weather(city: str)

    • 使用 httpx.AsyncClient() 发送异步 GET 请求到 OpenWeather API。

    • 如果请求成功,则调用 response.json() 返回一个字典。

    • 出现异常时,返回包含错误信息的字典。

Part 2. 格式化天气数据

  • 函数 format_weather(data: dict | str)

    • 首先检查传入的数据是否为字符串,如果是,则使用 json.loads 将其转换为字典。

    • 检查数据中是否包含 "error" 字段,如果有,直接返回错误提示。

    • 使用 .get() 方法提取 name、sys.country、main.temp、main.humidity、wind.speed 和 weather[0].description 等数据,并为可能缺失的字段提供默认值。

    • 将提取的信息拼接成一个格式化字符串,方便阅读。

Part 3. MCP 工具 query_weather(city: str)

  • 函数 query_weather

    • 通过 @mcp.tool() 装饰器注册为 MCP 服务器的工具,使其能够被客户端调用。

    • 调用 fetch_weather(city) 获取天气数据,然后用 format_weather(data) 将数据格式化为易读文本,最后返回该字符串。

Part 4. 运行服务器

  • if __name__ == "__main__": 块

    • 调用 mcp.run(transport='stdio') 启动 MCP 服务器,采用标准 I/O 通信方式,等待客户端调用。

此外,上述代码有两个注意事项,

  1. query_weather函数的函数说明至关重要,相当于是此后客户端对函数进行识别的基本依据,因此需要谨慎编写;

  2. 当指定 transport='stdio' 运行 MCP 服务器时,客户端必须在启动时同时启动当前这个脚本,否则无法顺利通信。这是因为 stdio 模式是一种本地进程间通信(IPC,Inter-Process Communication)方式,它需要服务器作为子进程运行,并通过标准输入输出(stdin/stdout)进行数据交换。

因此,当我们编写完服务器后,并不能直接调用这个服务器,而是需要创建一个对应的能够进行stdio的客户端,才能顺利进行通信。

2. client创建流程

        接下来继续创建客户端来调用MCP服务器。

2.1 创建 MCP 客户端项目

# 创建项目目录
cd /root/autodl-tmp/MCP
uv init mcp-chatbot
cd mcp-chatbot

图片

图片

2.2 创建MCP客户端虚拟环境

# 创建虚拟环境
uv venv

# 激活虚拟环境
source .venv/bin/activate

图片

这里需要注意的是,相比pip,uv会自动识别当前项目主目录并创建虚拟环境。

然后即可通过add方法在虚拟环境中安装相关的库。

# 安装 MCP SDK
uv add mcp openai python-dotenv httpx

图片

接下来创建.env文件,并写入OpenAI的API-Key,以及反向代理地址。借助反向代理,国内可以无门槛直连OpenAI官方服务器,并调用官方API。

图片

写入如下内容

BASE_URL="反向代理地址"
MODEL=gpt-4o
OPENAI_API_KEY="OpenAI-API-Key"

图片

OpenAI注册指南与国内反向代理领取地址:

而如果是使用DeepSeek模型,则需要在.env中写入如下内容:

BASE_URL=https://api.deepseek.com
MODEL=deepseek-chat      
OPENAI_API_KEY="DeepSeek API-Key"

创建servers_config.json

图片

创建weather_server.py

图片

并写入如下内容:

import os
import json
import httpx
from typing import Any
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP

# 初始化 MCP 服务器
mcp = FastMCP("WeatherServer")

# OpenWeather API 配置
OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5/weather"
API_KEY = "OPENWEATHER_API_KEY"
USER_AGENT = "weather-app/1.0"

async def fetch_weather(city: str) -> dict[str, Any] | None:
    """
    从 OpenWeather API 获取天气信息。
    :param city: 城市名称(需使用英文,如 Beijing)
    :return: 天气数据字典;若出错返回包含 error 信息的字典
    """
    params = {
        "q": city,
        "appid": API_KEY,
        "units": "metric",
        "lang": "zh_cn"
    }
    headers = {"User-Agent": USER_AGENT}

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(OPENWEATHER_API_BASE, params=params, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()  # 返回字典类型
        except httpx.HTTPStatusError as e:
            return {"error": f"HTTP 错误: {e.response.status_code}"}
        except Exception as e:
            return {"error": f"请求失败: {str(e)}"}

def format_weather(data: dict[str, Any] | str) -> str:
    """
    将天气数据格式化为易读文本。
    :param data: 天气数据(可以是字典或 JSON 字符串)
    :return: 格式化后的天气信息字符串
    """
    # 如果传入的是字符串,则先转换为字典
    if isinstance(data, str):
        try:
            data = json.loads(data)
        except Exception as e:
            return f"无法解析天气数据: {e}"

    # 如果数据中包含错误信息,直接返回错误提示
    if "error" in data:
        return f"⚠️ {data['error']}"

    # 提取数据时做容错处理
    city = data.get("name", "未知")
    country = data.get("sys", {}).get("country", "未知")
    temp = data.get("main", {}).get("temp", "N/A")
    humidity = data.get("main", {}).get("humidity", "N/A")
    wind_speed = data.get("wind", {}).get("speed", "N/A")
    # weather 可能为空列表,因此用 [0] 前先提供默认字典
    weather_list = data.get("weather", [{}])
    description = weather_list[0].get("description", "未知")

    return (
        f"🌍 {city}, {country}\n"
        f"🌡 温度: {temp}°C\n"
        f"💧 湿度: {humidity}%\n"
        f"🌬 风速: {wind_speed} m/s\n"
        f"🌤 天气: {description}\n"
    )

@mcp.tool()
async def query_weather(city: str) -> str:
    """
    输入指定城市的英文名称,返回今日天气查询结果。
    :param city: 城市名称(需使用英文)
    :return: 格式化后的天气信息
    """
    data = await fetch_weather(city)
    return format_weather(data)

if __name__ == "__main__":
    # 以标准 I/O 方式运行 MCP 服务器
    mcp.run(transport='stdio')

这段 JSON 配置文件描述了一个名为 weather 的 MCP 服务器(mcpServers),该服务器使用 Python 运行一个脚本 weather_server.py。下面是对每个部分的详细解释:

  1. mcpServers: 这是一个包含多个 MCP 服务器配置的对象。每个服务器都有一个唯一的名称,例如 weather,用来标识该服务器。

  2. weather: 这是 mcpServers 下的一个服务器配置。服务器的名称为 weather,你可以根据这个名称在其他地方引用该服务器。

  3. command: 这是启动该服务器所需要执行的命令。在这里,命令是 python,意味着这个服务器将通过 Python 运行。

  4. args: 这是一个数组,包含了传递给命令的参数。在这个例子中,传递的参数是 ["weather_server.py"],意味着该服务器将运行 weather_server.py 脚本。

然后在config.json中写入如下内容:

{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["weather_server.py"]
    }
  }
}

紧接着创建main.py,并写入如下内容:

import asyncio
import json
import logging
import os
import shutil
from contextlib import AsyncExitStack
from typing import Any, Dict, List, Optional

import httpx
from dotenv import load_dotenv
from openai import OpenAI  # OpenAI Python SDK
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# Configure logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)


# =============================
# 配置加载类(支持环境变量及配置文件)
# =============================
class Configuration:
    """管理 MCP 客户端的环境变量和配置文件"""

    def __init__(self) -> None:
        load_dotenv()
        # 从环境变量中加载 API key, base_url 和 model
        self.api_key = os.getenv("LLM_API_KEY")
        self.base_url = os.getenv("BASE_URL")
        self.model = os.getenv("MODEL")
        if not self.api_key:
            raise ValueError("❌ 未找到 LLM_API_KEY,请在 .env 文件中配置")

    @staticmethod
    def load_config(file_path: str) -> Dict[str, Any]:
        """
        从 JSON 文件加载服务器配置
        
        Args:
            file_path: JSON 配置文件路径
        
        Returns:
            包含服务器配置的字典
        """
        with open(file_path, "r") as f:
            return json.load(f)


# =============================
# MCP 服务器客户端类
# =============================
class Server:
    """管理单个 MCP 服务器连接和工具调用"""

    def __init__(self, name: str, config: Dict[str, Any]) -> None:
        self.name: str = name
        self.config: Dict[str, Any] = config
        self.session: Optional[ClientSession] = None
        self.exit_stack: AsyncExitStack = AsyncExitStack()
        self._cleanup_lock = asyncio.Lock()

    async def initialize(self) -> None:
        """初始化与 MCP 服务器的连接"""
        # command 字段直接从配置获取
        command = self.config["command"]
        if command is None:
            raise ValueError("command 不能为空")

        server_params = StdioServerParameters(
            command=command,
            args=self.config["args"],
            env={**os.environ, **self.config["env"]} if self.config.get("env") else None,
        )
        try:
            stdio_transport = await self.exit_stack.enter_async_context(
                stdio_client(server_params)
            )
            read_stream, write_stream = stdio_transport
            session = await self.exit_stack.enter_async_context(
                ClientSession(read_stream, write_stream)
            )
            await session.initialize()
            self.session = session
        except Exception as e:
            logging.error(f"Error initializing server {self.name}: {e}")
            await self.cleanup()
            raise

    async def list_tools(self) -> List[Any]:
        """获取服务器可用的工具列表

        Returns:
            工具列表
        """
        if not self.session:
            raise RuntimeError(f"Server {self.name} not initialized")
        tools_response = await self.session.list_tools()
        tools = []
        for item in tools_response:
            if isinstance(item, tuple) and item[0] == "tools":
                for tool in item[1]:
                    tools.append(Tool(tool.name, tool.description, tool.inputSchema))
        return tools

    async def execute_tool(
        self, tool_name: str, arguments: Dict[str, Any], retries: int = 2, delay: float = 1.0
    ) -> Any:
        """执行指定工具,并支持重试机制

        Args:
            tool_name: 工具名称
            arguments: 工具参数
            retries: 重试次数
            delay: 重试间隔秒数

        Returns:
            工具调用结果
        """
        if not self.session:
            raise RuntimeError(f"Server {self.name} not initialized")
        attempt = 0
        while attempt < retries:
            try:
                logging.info(f"Executing {tool_name} on server {self.name}...")
                result = await self.session.call_tool(tool_name, arguments)
                return result
            except Exception as e:
                attempt += 1
                logging.warning(
                    f"Error executing tool: {e}. Attempt {attempt} of {retries}."
                )
                if attempt < retries:
                    logging.info(f"Retrying in {delay} seconds...")
                    await asyncio.sleep(delay)
                else:
                    logging.error("Max retries reached. Failing.")
                    raise

    async def cleanup(self) -> None:
        """清理服务器资源"""
        async with self._cleanup_lock:
            try:
                await self.exit_stack.aclose()
                self.session = None
            except Exception as e:
                logging.error(f"Error during cleanup of server {self.name}: {e}")


# =============================
# 工具封装类
# =============================
class Tool:
    """封装 MCP 返回的工具信息"""

    def __init__(self, name: str, description: str, input_schema: Dict[str, Any]) -> None:
        self.name: str = name
        self.description: str = description
        self.input_schema: Dict[str, Any] = input_schema

    def format_for_llm(self) -> str:
        """生成用于 LLM 提示的工具描述"""
        args_desc = []
        if "properties" in self.input_schema:
            for param_name, param_info in self.input_schema["properties"].items():
                arg_desc = f"- {param_name}: {param_info.get('description', 'No description')}"
                if param_name in self.input_schema.get("required", []):
                    arg_desc += " (required)"
                args_desc.append(arg_desc)
        return f"""
Tool: {self.name}
Description: {self.description}
Arguments:
{chr(10).join(args_desc)}
"""


# =============================
# LLM 客户端封装类(使用 OpenAI SDK)
# =============================
class LLMClient:
    """使用 OpenAI SDK 与大模型交互"""

    def __init__(self, api_key: str, base_url: Optional[str], model: str) -> None:
        self.client = OpenAI(api_key=api_key, base_url=base_url)
        self.model = model

    def get_response(self, messages: List[Dict[str, Any]], tools: Optional[List[Dict[str, Any]]] = None) -> Any:
        """
        发送消息给大模型 API,支持传入工具参数(function calling 格式)
        """
        payload = {
            "model": self.model,
            "messages": messages,
            "tools": tools,
        }
        try:
            response = self.client.chat.completions.create(**payload)
            return response
        except Exception as e:
            logging.error(f"Error during LLM call: {e}")
            raise


# =============================
# 多服务器 MCP 客户端类(集成配置文件、工具格式转换与 OpenAI SDK 调用)
# =============================
class MultiServerMCPClient:
    def __init__(self) -> None:
        """
        管理多个 MCP 服务器,并使用 OpenAI Function Calling 风格的接口调用大模型
        """
        self.exit_stack = AsyncExitStack()
        config = Configuration()
        self.openai_api_key = config.api_key
        self.base_url = config.base_url
        self.model = config.model
        self.client = LLMClient(self.openai_api_key, self.base_url, self.model)
        # (server_name -> Server 对象)
        self.servers: Dict[str, Server] = {}
        # 各个 server 的工具列表
        self.tools_by_server: Dict[str, List[Any]] = {}
        self.all_tools: List[Dict[str, Any]] = []

    async def connect_to_servers(self, servers_config: Dict[str, Any]) -> None:
        """
        根据配置文件同时启动多个服务器并获取工具
        servers_config 的格式为:
        {
          "mcpServers": {
              "sqlite": { "command": "uvx", "args": [ ... ] },
              "puppeteer": { "command": "npx", "args": [ ... ] },
              ...
          }
        }
        """
        mcp_servers = servers_config.get("mcpServers", {})
        for server_name, srv_config in mcp_servers.items():
            server = Server(server_name, srv_config)
            await server.initialize()
            self.servers[server_name] = server
            tools = await server.list_tools()
            self.tools_by_server[server_name] = tools

            for tool in tools:
                # 统一重命名:serverName_toolName
                function_name = f"{server_name}_{tool.name}"
                self.all_tools.append({
                    "type": "function",
                    "function": {
                        "name": function_name,
                        "description": tool.description,
                        "input_schema": tool.input_schema
                    }
                })

        # 转换为 OpenAI Function Calling 所需格式
        self.all_tools = await self.transform_json(self.all_tools)

        logging.info("\n✅ 已连接到下列服务器:")
        for name in self.servers:
            srv_cfg = mcp_servers[name]
            logging.info(f"  - {name}: command={srv_cfg['command']}, args={srv_cfg['args']}")
        logging.info("\n汇总的工具:")
        for t in self.all_tools:
            logging.info(f"  - {t['function']['name']}")

    async def transform_json(self, json_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        将工具的 input_schema 转换为 OpenAI 所需的 parameters 格式,并删除多余字段
        """
        result = []
        for item in json_data:
            if not isinstance(item, dict) or "type" not in item or "function" not in item:
                continue
            old_func = item["function"]
            if not isinstance(old_func, dict) or "name" not in old_func or "description" not in old_func:
                continue
            new_func = {
                "name": old_func["name"],
                "description": old_func["description"],
                "parameters": {}
            }
            if "input_schema" in old_func and isinstance(old_func["input_schema"], dict):
                old_schema = old_func["input_schema"]
                new_func["parameters"]["type"] = old_schema.get("type", "object")
                new_func["parameters"]["properties"] = old_schema.get("properties", {})
                new_func["parameters"]["required"] = old_schema.get("required", [])
            new_item = {
                "type": item["type"],
                "function": new_func
            }
            result.append(new_item)
        return result

    async def chat_base(self, messages: List[Dict[str, Any]]) -> Any:
        """
        使用 OpenAI 接口进行对话,并支持多次工具调用(Function Calling)。
        如果返回 finish_reason 为 "tool_calls",则进行工具调用后再发起请求。
        """
        response = self.client.get_response(messages, tools=self.all_tools)
        # 如果模型返回工具调用
        if response.choices[0].finish_reason == "tool_calls":
            while True:
                messages = await self.create_function_response_messages(messages, response)
                response = self.client.get_response(messages, tools=self.all_tools)
                if response.choices[0].finish_reason != "tool_calls":
                    break
        return response

    async def create_function_response_messages(self, messages: List[Dict[str, Any]], response: Any) -> List[Dict[str, Any]]:
        """
        将模型返回的工具调用解析执行,并将结果追加到消息队列中
        """
        function_call_messages = response.choices[0].message.tool_calls
        messages.append(response.choices[0].message.model_dump())
        for function_call_message in function_call_messages:
            tool_name = function_call_message.function.name
            tool_args = json.loads(function_call_message.function.arguments)
            # 调用 MCP 工具
            function_response = await self._call_mcp_tool(tool_name, tool_args)
            messages.append({
                "role": "tool",
                "content": function_response,
                "tool_call_id": function_call_message.id,
            })
        return messages

    async def process_query(self, user_query: str) -> str:
        """
        OpenAI Function Calling 流程:
         1. 发送用户消息 + 工具信息
         2. 若模型返回 finish_reason 为 "tool_calls",则解析并调用 MCP 工具
         3. 将工具调用结果返回给模型,获得最终回答
        """
        messages = [{"role": "user", "content": user_query}]
        response = self.client.get_response(messages, tools=self.all_tools)
        content = response.choices[0]
        logging.info(content)
        if content.finish_reason == "tool_calls":
            tool_call = content.message.tool_calls[0]
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments)
            logging.info(f"\n[ 调用工具: {tool_name}, 参数: {tool_args} ]\n")
            result = await self._call_mcp_tool(tool_name, tool_args)
            messages.append(content.message.model_dump())
            messages.append({
                "role": "tool",
                "content": result,
                "tool_call_id": tool_call.id,
            })
            response = self.client.get_response(messages, tools=self.all_tools)
            return response.choices[0].message.content
        return content.message.content

    async def _call_mcp_tool(self, tool_full_name: str, tool_args: Dict[str, Any]) -> str:
        """
        根据 "serverName_toolName" 格式调用相应 MCP 工具
        """
        parts = tool_full_name.split("_", 1)
        if len(parts) != 2:
            return f"无效的工具名称: {tool_full_name}"
        server_name, tool_name = parts
        server = self.servers.get(server_name)
        if not server:
            return f"找不到服务器: {server_name}"
        resp = await server.execute_tool(tool_name, tool_args)
        return resp.content if resp.content else "工具执行无输出"

    async def chat_loop(self) -> None:
        """多服务器 MCP + OpenAI Function Calling 客户端主循环"""
        logging.info("\n🤖 多服务器 MCP + Function Calling 客户端已启动!输入 'quit' 退出。")
        messages: List[Dict[str, Any]] = []
        while True:
            query = input("\n你: ").strip()
            if query.lower() == "quit":
                break
            try:
                messages.append({"role": "user", "content": query})
                messages = messages[-20:]  # 保持最新 20 条上下文
                response = await self.chat_base(messages)
                messages.append(response.choices[0].message.model_dump())
                result = response.choices[0].message.content
                # logging.info(f"\nAI: {result}")
                print(f"\nAI: {result}")
            except Exception as e:
                print(f"\n⚠️  调用过程出错: {e}")

    async def cleanup(self) -> None:
        """关闭所有资源"""
        await self.exit_stack.aclose()


# =============================
# 主函数
# =============================
async def main() -> None:
    # 从配置文件加载服务器配置
    config = Configuration()
    servers_config = config.load_config("servers_config.json")
    client = MultiServerMCPClient()
    try:
        await client.connect_to_servers(servers_config)
        await client.chat_loop()
    finally:
        try:
            await asyncio.sleep(0.1)
            await client.cleanup()
        except RuntimeError as e:
            # 如果是因为退出 cancel scope 导致的异常,可以选择忽略
            if "Attempted to exit cancel scope" in str(e):
                logging.info("退出时检测到 cancel scope 异常,已忽略。")
            else:
                raise

if __name__ == "__main__":
    asyncio.run(main())

这是一个基于 MCP(Model Context Protocol) 协议和 OpenAI Function Calling 机制的多服务器客户端实现。它集成了多个服务器,并支持通过 OpenAI 模型与这些服务器进行交互,执行工具和函数调用。下面是对代码各部分的详细解释:

1. 导入的库

  • asyncio: Python 中的异步编程库,用于处理异步任务。

  • json: 处理 JSON 格式数据。

  • logging: 配置日志输出。

  • os: 与操作系统交互,读取环境变量等。

  • shutil: 提供文件操作的高层接口(未使用在代码中)。

  • contextlib: 提供异步上下文管理功能。

  • httpx: 用于 HTTP 请求的异步客户端库(未直接使用,但可能用于未来扩展)。

  • dotenv: 从 .env 文件加载环境变量。

  • openai: OpenAI Python SDK,用于与 OpenAI API 进行交互。

  • mcp: MCP 协议的客户端库,用于与 MCP 服务器交互。

2. 配置加载类 (Configuration)

  • 功能: 管理 MCP 客户端的环境变量和配置文件。

  • 方法:

    • __init__: 从 .env 文件加载环境变量,获取 API 密钥 (LLM_API_KEY)、基本 URL (BASE_URL) 和模型名称 (MODEL)。

    • load_config: 从给定路径加载 JSON 配置文件,返回包含配置的字典。

3. MCP 服务器客户端类 (Server)

  • 功能: 管理与 MCP 服务器的连接及与工具的交互。

  • 方法:

    • initialize: 初始化与 MCP 服务器的连接,使用 stdio_client 与服务器建立连接。

    • list_tools: 获取服务器可用的工具列表。

    • execute_tool: 执行指定的工具,支持重试机制。

    • cleanup: 清理资源,关闭与 MCP 服务器的连接。

4. 工具封装类 (Tool)

  • 功能: 封装从 MCP 服务器获取的工具信息。

  • 方法:

    • format_for_llm: 将工具信息转换为适合 OpenAI LLM(大语言模型)提示的格式,包含工具的名称、描述和输入参数。

5. LLM 客户端类 (LLMClient)

  • 功能: 使用 OpenAI SDK 与大模型进行交互。

  • 方法:

    • get_response: 向 OpenAI 模型发送消息,支持传入工具参数(即 Function Calling 格式)。

6. 多服务器 MCP 客户端类 (MultiServerMCPClient)

  • 功能: 管理多个 MCP 服务器,并使用 OpenAI Function Calling 机制与大模型进行交互。

  • 方法:

    • connect_to_servers: 根据配置文件启动多个服务器,获取并列出每个服务器的工具。

    • transform_json: 将 MCP 工具的输入 schema 转换为 OpenAI Function Calling 所需的格式。

    • chat_base: 发送用户消息给 OpenAI 模型,支持多个工具调用。

    • create_function_response_messages: 解析模型返回的工具调用并执行。

    • process_query: 处理用户查询,支持模型调用多个工具并返回结果。

    • chat_loop: 客户端主循环,处理与用户的交互,支持多次工具调用。

    • cleanup: 关闭所有资源。

7. 主函数 (main)

  • 功能: 主程序入口,加载服务器配置并启动客户端。

  • 方法:

    • 从配置文件加载服务器配置。

    • 使用 MultiServerMCPClient 启动服务器并开始与用户的聊天交互。

    • 在程序结束时进行清理操作。

8. 程序流

  1. 配置加载:程序首先加载 .env 文件中的配置和 JSON 配置文件中的服务器配置。

  2. 连接服务器:通过 MultiServerMCPClient 初始化多个 MCP 服务器并获取每个服务器的可用工具。

  3. 与模型交互:通过 OpenAI SDK 与模型交互,支持模型调用工具和执行功能。

  4. 功能调用:当模型返回要求调用工具时,程序会执行对应的 MCP 服务器工具并返回结果,直到最终得到模型的回答。

  5. 聊天循环:用户通过控制台输入查询,程序调用 OpenAI 模型并执行必要的工具调用,直到返回最终结果。

9. 异常处理

  • 代码中有多处使用 try-except 来捕获和处理异常,确保在服务器连接或工具调用过程中发生错误时可以优雅地处理,并输出错误信息。

4.运行测试

        最后即可在当前项目的主目录下输入uv run进行运行:

uv run main.py

图片

可以进行多轮对话并进行天气查询:

图片

并支持多工具并行调用:

图片

图片

5.接入更多工具

接下来在当前主目录下创建write_server.py服务器:

图片

图片

同时写入配置文件:

图片

即可在对话中调用新的MCP工具了(需重启对话):

图片

图片


MCP服务器上线流程参考

1 MPC服务器在线管理与实时下载

1.1 npm registry 简介介绍

        npm registry(Node Package Manager Registry)是一个 开源的 JavaScript 包管理平台,它存储着成千上万的 JavaScript 和 Node.js 库、工具和框架。开发者可以将自己的代码库作为包发布到 npm registry,供其他开发者使用。它是 npm (Node Package Manager)工具的核心组件,npm 是当前最流行的 JavaScript 包管理工具,广泛应用于前端和后端开发中。

        npm registry 的作用是为 JavaScript/Node.js 开发者 提供一个集中的资源库,用户可以通过 npm 或 npx 等工具来安装、更新和使用这些包。除此之外,npm registry 还支持其他语言的工具和脚本,比如通过 uvx,Python 工具也能方便地通过 npm registry 进行下载和管理。

        使用 npm registry 带来了许多便捷性,尤其是在 实时下载和管理库 方面,它改变了开发者与依赖管理的互动方式。以下是它带来的几个主要优点:

  1. 无需手动下载和安装依赖:

    • 通过 npm 或 npx,开发者可以轻松地 实时下载并运行 所需的包,无需手动下载、解压和安装依赖项。npx 甚至支持临时下载并执行工具,而不必安装到本地环境中,减少了不必要的手动操作。

  2. 最新版本的实时访问:

    • 使用 npm registry 时,您始终能够访问到 最新版本 的工具和库。当发布新版本时,用户通过 npx 或 npm install 安装的包会自动下载最新的版本,避免了使用过时版本的风险。

  3. 集中管理和共享:

    • npm registry 提供了一个集中管理和分发代码的场所,开发者可以方便地发布自己的工具、库,并与全球其他开发者共享。这促进了 开源生态系统 的发展,并且让其他开发者能够轻松使用这些工具。

  4. 跨语言支持(通过 uvx):

    • 尽管 npm registry 是以 JavaScript/Node.js 为主,但通过 uvx 等工具,它也可以方便地管理 Python 包 和其他语言的工具,这使得 跨语言开发 更加简洁和高效。

  5. 简化依赖管理和版本控制:

    • 在开发过程中,npm registry 不仅能帮助开发者快速获取第三方库,还能自动处理依赖版本的管理。通过 npm 配置文件(如 package.json),开发者可以清晰地查看和管理项目所依赖的所有库,并且可以随时更新、安装或回滚特定版本。

  6. 跨平台支持:

    • npm registry 支持的工具和包广泛适用于不同操作系统(如 Windows、macOS、Linux 等)。无论在哪个平台上,开发者都可以使用相同的命令来获取并运行所需的工具,无需担心操作系统的差异。

        npm registry 提供了一个集中、开放、实时更新的生态系统,极大地简化了开发者在项目中使用外部工具和库的过程。开发者只需要通过简单的命令(如 npm install 或 npx),就能实时下载最新版本的库、工具和框架,而无需处理繁琐的版本管理和依赖配置。实时下载和运行工具包的便捷性,使得开发工作更加高效,能够快速迭代和创新,同时促进了开源社区的蓬勃发展。

        通过 npm registry 和 npx 等工具,开发者可以轻松使用最新的工具包,同时避免了手动管理和安装包的麻烦。这使得跨语言和跨平台的开发变得更加简便,增强了开发效率。

1.2 将开发好的库上传至npm registry

        接下来我们尝试将一个 Python 编写的 MCP 服务器 发布为一个 npm 包,并能够通过 npx 或 uvx 快速运行该服务器。这种方法使得您可以跨平台发布和使用 Python 脚本,而不需要其他开发者手动安装和配置 Python 环境。

步骤 1: 准备 Python 代码

首先需要编写一个Python脚本,也就是一个MCP服务器。以查询天气为例,具体代码我们稍后会进行解释,这里我们可以先在课件网盘中下载一个自定义的天气查询MCP服务器Python脚本:

图片

图片

然后即可继续进行发布操作。

步骤 2: 创建一个 Node.js 项目

  1. 初始化 Node.js 项目:首先,我们需要一个 package.json 文件,这是 npm 包的核心配置文件。我们可以通过 npm init 命令来初始化一个新的 Node.js 项目。打开终端,进入到项目文件夹,然后运行以下命令:

npm init
  1. 这将引导您我们创建一个新的 package.json 文件。在提问时,我们可以按默认值按下 Enter,或者输入我们自定义内容。

图片

  1. 安装 uvx 工具: uvx 是一个用于 Python 脚本和工具管理的工具,可以让我们通过 npm 来管理和运行 Python 工具包。

  2. 运行以下命令来安装 uvx:

npm install uvx --save

步骤 3: 配置 package.json 来运行 Python 脚本

  1. 在 package.json 文件中,添加一个 bin 字段,告诉 npm 包如何启动我们的 Python 脚本。

  2. 打开 package.json 文件,并将其修改为类似下面的样子:

{
  "name": "weather-server",
  "version": "1.0.0",
  "description": "A weather server that fetches weather data from OpenWeather API",
  "main": "index.js",
  "bin": {
    "mcp-server-git": "./weather_server.py"
  },
  "dependencies": {
    "uvx": "^latest"
  },
  "scripts": {
    "start": "uvx weather-server"
  },
  "author": "",
  "license": "ISC"
}
  • bin 字段:将我们的 Python 脚本路径指定为命令。这里,"mcp-server-git" 将成为用户运行命令时执行的脚本名称,"./weather_server.py" 指定 Python 脚本路径。

  • scripts 字段:指定使用 uvx 启动 Python 脚本。

图片

  1. 创建一个简单的启动脚本:在项目根目录下创建一个简单的 index.js 文件来调用 Python 脚本。

  2. index.js 文件:

const { exec } = require('child_process');

exec('python weather_server.py --api_key YOUR_API_KEY', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});
  1. 这个脚本将运行我们的 Python 脚本并传递 API Key。

图片

步骤 4: 创建一个 .npmignore 文件

如果我们的项目包含不需要发布到 npm 的文件(如 Python 环境相关的文件、缓存文件等),可以在项目根目录创建一个 .npmignore 文件,并列出这些文件。例如:

*.pyc
__pycache__
*.env

步骤 5: 发布包到 npm

  1. 登录 npm:如果您还没有 npm 账户,首先需要在 npm 官网 注册一个账户。

图片

  1. 登录您的 npm 账户:

npm login

图片

  1. 注意这里需要访问 npm 官方网站:https://www.npmjs.com/signup进行注册,并且设置npm为官方镜像源:

npm config set registry https://registry.npmjs.org/
  1. 然后才能顺利的登录和发布。

  2. 发布到 npm:使用以下命令将您的包发布到 npm registry:

npm publish
  1. 这将把我们的包上传到 npm registry,其他用户就可以通过 npx 或 uvx 下载并运行您的 Python 服务器了。

图片

步骤 6: 使用 npx 或 uvx 来运行 MCP 服务器

        发布成功后,我们尝试在Cherry Studio中运行这个天气查询服务器。

<think>好的,我现在需要帮助用户在Windows系统上本地部署MCP(Micro Cloud Platform)。首先,我需要确认MCP的具体定义和相关技术背景,因为可能有不同的项目或产品使用相同的缩写。根据用户提供的引用,特别是引用[2]提到的微软和阿里云发布的Open Application Model(OAM),可能与云原生应用部署相关,因此推测MCP可能是指微型的云平台,如基于OAM的应用部署平台,或者类似MicroK8s、Minikube这样的轻量级Kubernetes环境。 接下来,我需要查找是否有官方文档或社区资源支持在Windows上部署MCP。如果用户指的是微软和阿里云的OAM相关平台,可能需要使用如Docker Desktop、Kubernetes等工具。例如,使用Docker和Kubernetes来搭建本地微云环境。 然后,考虑部署步骤。通常,这类部署需要安装容器运行时(如Docker)、配置Kubernetes集群(如通过Minikube或Kind)、安装OAM相关的控制器或工具(如Rudr,个OAM的实现)。需要确保这些步骤在Windows上的可行性,比如Docker Desktop支持Windows,但需要启用WSL2或Hyper-V。 另外,用户可能需要具体的命令行操作,比如安装Docker,启用Kubernetes,部署MCP组件,配置网络等。需要注意权限问题,以及Windows特有的路径和配置方式。同时,要提醒用户可能遇到的常见问题,比如网络配置错误、服务无法启动等,并提供排查方法,如检查日志、验证Pod状态等。 最后,根据用户提供的引用,可能需要引用相关资源,如OAM的官方文档、微软的部署指南,或者阿里云的相关教程,确保信息的准确性。如果MCP有特定的安装包或脚本,也需要指导用户正确使用,并注意安全事项,如避免使用未经认证的源。 总结步骤可能包括:安装依赖工具(Docker、Kubernetes)、配置环境、部署MCP组件、验证安装,以及后续管理和应用部署。需要分步骤详细说明,并给出示例命令,同时参考引用中的信息来增强权威性。</think>在Windows系统中本地部署MCP(Micro Cloud Platform)通常涉及容器化技术和云原生工具的配置。以下是分步指南: ### 步骤1:安装基础依赖 1. **启用Hyper-V或WSL2** - 在Windows功能中启用Hyper-V(专业版/企业版适用)或安装WSL2(家庭版替代方案)。 - 管理员身份运行PowerShell: ```powershell wsl --install ``` 2. **安装Docker Desktop** - 下载[Docker Desktop for Windows](https://www.docker.com/products/docker-desktop),安装后设置使用WSL2后端,并在设置中启用Kubernetes集群[^2]。 ### 步骤2:配置Kubernetes环境 1. **启动Kubernetes** - 在Docker Desktop设置界面勾选"Enable Kubernetes",等待集群初始化完成。 2. **验证集群状态** ```powershell kubectl cluster-info kubectl get nodes # 应显示个名为"docker-desktop"的节点 ``` ### 步骤3部署MCP核心组件 1. **安装OAM控制器(如Rudr)** ```powershell helm repo add oam https://oam.dev/helm-charts helm install rudr oam/rudr ``` 2. **部署MCP管理平面** - 根据具体MCP实现(如阿里云提供的方案),通常需要YAML文件定义组件: ```yaml # mcp-core.yaml示例 apiVersion: core.oam.dev/v1alpha1 kind: Application metadata: name: mcp-application spec: components: - name: mcp-server type: webservice properties: image: mcp-registry.example/mcp-server:v1.2.3 ports: - port: 8080 ``` - 应用配置: ```powershell kubectl apply -f mcp-core.yaml ``` ### 步骤4:网络配置与访问 1. **暴露服务端口** ```powershell kubectl expose deployment mcp-server --type=NodePort --port=8080 ``` 2. **获取访问地址** ```powershell minikube service mcp-server --url # 若使用Minikube # 或通过Docker Desktop的端口转发查看 ``` ### 常见问题排查 - **组件未就绪**:使用`kubectl describe pod/<pod-name>`查看事件日志 - **网络连接失败**:检查Windows防火墙是否放行Docker相关进程 - **存储配置**:通过`kubectl apply -f persistent-volume.yaml`添加持久化存储
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值