手把手MCP教学-统一配置管理本地和线上服务器

前言

上一篇文章(手把手MCP教学-客户端连接多服务器),配置了MCP客户端连接多服务器的能力,本篇文章将基于此构建通过配置文件统一管理服务器。

配置完成之后实现的效果就是不用我们每次使用客户端还要手动的去启动对应的本地服务器,并且通过配置文件连接线上的服务器,例如高德地图MCP和百度地图MCP。——就像Cursor集成的MCP配置服务。

本次改动完全是对于客户端的优化,不改动服务器,话不多说,直接开始。

MCP客户端优化

MCP客户端想要实现统一配置和连接线上服务主要包括以下改动:

  1. 新建服务器JSON配置文件,统一管理需要连接的MCP服务端。
  1. 对CMP客户端的服务端连接方法和工具整合进行优化,从原本的手动引入变成根据配置文件连接。

直接上代码:

1. 新建服务器配置文件

新建JSON配置文件:client/mcp_client/mcp_servers.json

高德地图MCP和百度地图MCP的密钥请自行到他们的开放平台获取

json
{
    "mcpServers": {
        "amap-maps": {
            "type": "local",
            "command": "npx",
            "args": [
                "-y",
                "@amap/amap-maps-mcp-server"
            ],
            "env": {
                "AMAP_MAPS_API_KEY": "
你的密钥"
            }
        },
        "baidu-maps": {
            "type": "local",
            "command": "uvx",
            "args": ["mcp-server-baidu-maps"],
            "env": {
                "BAIDU_MAPS_API_KEY": "你的密钥"
            }
        },
        "weather": {
            "type": "local",
            "command": "uv",
            "args": [
                "--directory",
                "G:\\MCP\\mcp-agent-project\\server\\weather",  // 你的本地MCP服务地址
                "run",
                "weather.py"
            ]
        },
        "filesystem": {
            "type": "local",
            "command": "uv",
            "args": [
                "--directory",
                "G:\\MCP\\mcp-agent-project\\server\\filesystem", // 你的本地MCP服务地址
                "run",
                "filesystem.py"
            ]
        }
    }
}

2. 优化MCP客户端程序

安装依赖包

PowerShell
cd client
cd mcp-client

uv venv
.venv\Scripts\activate

uv add aiohttp  #
用于对接基于SSE的MCP服务

优化后程序

新建一个client_tools_ol.py客户端文件

python
import asyncio
import os
from openai import OpenAI
from dotenv import load_dotenv
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import json
import aiohttp  #
用于云端服务的 HTTP/WebSocket 连接

# 加载 .env 文件
load_dotenv()

class MCPClient:
    def __init__(self):
        """初始化 MCP 客户端"""
        self.exit_stack = AsyncExitStack()
        self.api_key = os.getenv("API_KEY")  # 读取 OpenAI API Key
        self.base_url = os.getenv("BASE_URL")  # 读取 BASE URL
        self.model = os.getenv("MODEL")  # 读取 model
       
        if not self.api_key:
            raise ValueError("未找到 API KEY. 请在 .env 文件中配置 API_KEY")

        self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
        self.sessions = {}  # 存储多个服务端会话
        self.tools_map = {}  # 工具映射:工具名称 -> 服务端 ID

    async def load_servers_from_config(self, config_path: str):
        """
        从配置文件加载 MCP 服务
        :param config_path: 配置文件路径
        """
        with open(config_path, 'r', encoding='utf-8') as f:
            config = json.load(f)
   
        servers = config.get("mcpServers", {})
        for server_id, server_config in servers.items():
            service_type = server_config.get("type", "local")
            if service_type in ["sse", "websocket"]:
                # 云端服务
                await self.connect_to_cloud_server(server_id, server_config["url"], service_type)
            elif service_type == "local":
                # 本地服务
                command = server_config["command"]
                args = server_config.get("args", [])
                env = server_config.get("env", None)
                await self.connect_to_local_server(server_id, command, args, env)

    async def connect_to_cloud_server(self, server_id: str, url: str, service_type: str):
        """
        连接到云端 MCP 服务
        :param server_id: 服务端标识符
        :param url: 云端服务的 URL
        :param service_type: 服务类型(sse 或 websocket)
        """
        if server_id in self.sessions:
            raise ValueError(f"服务端 {server_id} 已经连接")
   
        if service_type == "websocket":
            # 使用 aiohttp 建立 WebSocket 连接
            session = aiohttp.ClientSession()
            ws = await session.ws_connect(url)
            self.sessions[server_id] = {"session": ws, "type": "cloud-websocket"}
            print(f"已连接到云端 WebSocket 服务: {server_id}")
   
        elif service_type == "sse":
            # 使用 aiohttp 建立 SSE 连接
            session = aiohttp.ClientSession()
            response = await session.get(url)
            if response.status != 200:
                raise ValueError(f"无法连接到 SSE 服务 {server_id},状态码: {response.status}")
   
            self.sessions[server_id] = {"session": response, "type": "cloud-sse"}
            print(f"已连接到云端 SSE 服务: {server_id}")
   
        else:
            raise ValueError(f"未知的服务类型: {service_type}")

    async def connect_to_local_server(self, server_id: str, command: str, args: list, env: dict):
        """
        连接到本地 MCP 服务
        :param server_id: 服务端标识符
        :param command: 本地服务的启动命令
        :param args: 启动命令的参数
        :param env: 环境变量
        """
        if server_id in self.sessions:
            raise ValueError(f"服务端 {server_id} 已经连接")
   
        print(f"正在连接本地服务: {server_id}, 命令: {command}, 参数: {args}, 环境变量: {env}")
   
        server_params = StdioServerParameters(command=command, args=args, env=env)
   
        # 启动 MCP 服务器并建立通信
        stdio_transport = await self.exit_stack.enter_async_context(
            stdio_client(server_params))
        stdio, write = stdio_transport
        session = await self.exit_stack.enter_async_context(
            ClientSession(stdio, write))
   
        await session.initialize()
        self.sessions[server_id] = {"session": session, "type": "local"}
        print(f"已连接到本地 MCP 服务: {server_id}")
    async def handle_sse_messages(self, server_id: str):
        """
        处理 SSE 消息流
        :param server_id: 服务端标识符
        """
        session_info = self.sessions.get(server_id)
        if not session_info or session_info["type"] != "cloud-sse":
            raise ValueError(f"服务端 {server_id} 不是有效的 SSE 服务")
   
        response = session_info["session"]
        try:
            async for line in response.content:
                # SSE 消息以换行符分隔
                if line:
                    message = line.decode("utf-8").strip()
                    print(f"[SSE Message from {server_id}]: {message}")
        except Exception as e:
            print(f"SSE 消息处理错误: {e}")
   
    async def list_tools(self):
        """列出所有服务端的工具"""
        if not self.sessions:
            print("没有已连接的服务端")
            return
   
        print("已连接的服务端工具列表:")
        for server_id, session_info in self.sessions.items():
            if session_info["type"] == "local":
                session = session_info["session"]
                response = await session.list_tools()
                for tool in response.tools:
                    self.tools_map[tool.name] = server_id
                    print(f"工具: {tool.name}, 来源服务端: {server_id}")

    async def process_query(self, query: str) -> str:
        """
        调用大模型处理用户查询,并根据返回的 tools 列表调用对应工具。
        支持多次工具调用,直到所有工具调用完成。
        """
        messages = [{"role": "user", "content": query}]

        # 构建统一的工具列表
        available_tools = []
        for tool_name, server_id in self.tools_map.items():
            session = self.sessions[server_id]["session"]
            response = await session.list_tools()
            for tool in response.tools:
                if tool.name == tool_name:
                    available_tools.append({
                        "type": "function",
                        "function": {
                            "name": tool.name,
                            "description": tool.description,
                            "input_schema": tool.inputSchema
                        }
                    })

        # print('整合的服务端工具列表:', available_tools)

        # 循环处理工具调用
        while True:
            # 请求 OpenAI 模型处理
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=available_tools
            )

            # 处理返回的内容
            content = response.choices[0]
            if content.finish_reason == "tool_calls":
                # 执行工具调用
                for tool_call in content.message.tool_calls:
                    tool_name = tool_call.function.name
                    tool_args = json.loads(tool_call.function.arguments)

                    # 根据工具名称找到对应的服务端
                    server_id = self.tools_map.get(tool_name)
                    if not server_id:
                        raise ValueError(f"未找到工具 {tool_name} 对应的服务端")

                    session = self.sessions[server_id]["session"]
                    result = await session.call_tool(tool_name, tool_args)
                    print(f"\n\n[Calling tool {tool_name} on server {server_id} with args {tool_args}]\n\n")
                    print(f"[Tool {tool_name} Result]: {result.content[0].text}")
                   
                    # 将工具调用的结果添加到 messages 中
                    messages.append({
                        "role": "tool",
                        "content": result.content[0].text,
                        "tool_call_id": tool_call.id,
                    })

            else:
                # 如果没有工具调用,返回最终的回复
                return content.message.content
    async def call_tool(self, server_id: str, tool_name: str, tool_args: dict):
        """
        调用指定服务端的工具
        :param server_id: 服务端标识符
        :param tool_name: 工具名称
        :param tool_args: 工具参数
        """
        session_info = self.sessions.get(server_id)
        if not session_info:
            raise ValueError(f"服务端 {server_id} 未连接")
   
        session_type = session_info["type"]
        if session_type == "cloud-websocket":
            # WebSocket 工具调用
            ws = session_info["session"]
            await ws.send_json({"tool": tool_name, "args": tool_args})
            response = await ws.receive_json()
            return response
   
        elif session_type == "cloud-sse":
            # SSE 工具调用(通过 HTTP 请求触发)
            response = await session_info["session"].post(
                f"{session_info['url']}/{tool_name}",
                json=tool_args
            )
            if response.status != 200:
                raise ValueError(f"SSE 工具调用失败,状态码: {response.status}")
            return await response.json()
   
        elif session_type == "local":
            # 本地工具调用
            session = session_info["session"]
            return await session.call_tool(tool_name, tool_args)
   
        else:
            raise ValueError(f"未知的会话类型: {session_type}")
   
    async def chat_loop(self):
        """运行交互式聊天循环"""
        print("MCP 客户端已启动!输入 'exit' 退出")

        while True:
            try:
                query = input("问: ").strip()
                if query.lower() == 'exit':
                    break

                response = await self.process_query(query)
                print(f"AI回复: {response}")

            except Exception as e:
                print(f"发生错误: {str(e)}")

    async def clean(self):
        """清理所有资源"""
        await self.exit_stack.aclose()
        self.sessions.clear()
        self.tools_map.clear()

async def main():
    # 启动并初始化 MCP 客户端
    client = MCPClient()
    try:
        # 从配置文件加载 MCP 服务
        await client.load_servers_from_config("mcp_servers.json")
        # 列出 MCP 服务器上的工具
        await client.list_tools()
        # 运行交互式聊天循环,处理用户对话
        await client.chat_loop()
    finally:
        # 清理资源
        await client.clean()

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

测试

PowerShell
uv run client_tools_ol.py

我们尝试让大语言模型使用这些MCP工具,看看效果如何:

智谱-glm-4-9b-chat模型

它首先使用了我构建的天气预报MCP,而不是高德地图的MCP。

然后又调用了一次百度地图MCP获取定位——不知道意义是说明

然后又调用了我构建的天气预报MCP,又获取了一次东莞的天气——可见它的多次对话的记忆比较差。

最后调用了我构建的本地文件控制MCP,保存并写入内容

总的来说智谱-glm-4-9b-chat小模型能够调用工具,但是并不够“听话”,我们换一个比较大的模型试试。

DeepSeek-V3

DeepSeekV3首先调用了我的天气预报MCP获取的东莞的天气预报——怎么都调用我的,明明我专门提到使用高德地图MCP,可能是整合后的工具,或者高德地图MCP的天气预报服务自身没有标识高德,所以大模型都调用我的天气预报MCP。

第二步获取了惠州的天气预报

第三步是对天气预报数据进行了总结然后存储

DeepSeekV3-0324介绍中也有专门提到在工具使用这块有提升,看来确实如此。

两次都是使用了我的天气预报,这里我先关闭其他插件,试试MCP客户端连接线上MCP服务的效果。

也成功调用了高德MCP的服务。

至此我们完成了MCP客户端的通过配置文件统一管理服务端MCP的优化。

下期预告

  1. 连接本地LLM大模型-构建完全本地的Agent。
<think>好的,我现在需要帮助用户在Windows系统上本地部署MCP(Micro Cloud Platform)。首先,我需要确认MCP的具体定义相关技术背景,因为可能有不同的项目或产品使用相同的缩写。根据用户提供的引用,特别是引用[2]提到的微软阿里云发布的Open Application Model(OAM),可能与云原生应用部署相关,因此推测MCP可能是指微型的云平台,如基于OAM的应用部署平台,或者类似MicroK8s、Minikube这样的轻量级Kubernetes环境。 接下来,我需要查找是否有官方文档或社区资源支持在Windows上部署MCP。如果用户指的是微软阿里云的OAM相关平台,可能需要使用如Docker Desktop、Kubernetes等工具。例如,使用DockerKubernetes来搭建本地微云环境。 然后,考虑部署步骤。通常,这类部署需要安装容器运行时(如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`添加持久化存储
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值