如何使用MCP开发一个客户端和服务端

如何使用MCP开发一个客户端和服务端

一、MCP和API以及Function Call核心概念对比

特性APIFunction CallMCP (Model Context Protocol)
定位通用应用程序接口大模型原生扩展能力标准化模型-服务交互协议
耦合度与具体服务绑定与模型强绑定 (如 GPT-4-turbo)与模型解耦,跨平台通用
交互模式直接请求-响应模型生成结构化调用建议JSON-RPC 2.0 标准化通信
典型场景数据集成、微服务通信简单实时操作 (天气/订单查询)复杂异步任务 & 跨系统整合

二、 MCP 协议

1. 什么是MCP协议

模型上下文协议(Model Context Protocol)是一种专为大语言模型设计的标准化协议,它允许LLM以安全、一致的方式与外部系统交互。MCP协议常被描述为"AI的USB-C接口",提供了一种统一的方式连接LLM与它们可以使用的资源。

MCP协议的核心功能包括:

  • 资源(Resources):类似于GET端点,用于将信息加载到LLM的上下文中
  • 工具(Tools):类似于POST端点,用于执行代码或产生副作用
  • 提示(Prompts):可重用的LLM交互模板
  • 上下文(Context):提供额外的交互功能,如日志记录和进度报告

2. 核心价值

  • 标准化:统一 AI 与外部服务的交互格式,解决工具碎片化问题
  • 解耦设计:模型无需硬编码 API 逻辑,通过声明式函数描述调用服务
  • 异步支持:适用于多步骤工作流(如爬取数据→分析→存储)

3. 工作流程

MCP 大概的工作方式: Claude Desktop、Cursor 这些工具,在内部实现了 MCP Client,然后MCP Client 通过标准的 MCP 协议和 MCP Server 进行交互,由各种三方开发者提供的 MCP Server 负责实现各种和三方资源交互的逻辑,比如访问数据库、浏览器、本地文件,最终再通过 标准的 MCP 协议返回给 MCP Client,最终给用户进行展示。

下图是一个通过查询天气来简单展示其对应的工作方式:

User AI_Model MCP_Server External_Service “查询北京天气” 解析意图,生成MCP调用 { "function": "get_weather", "params": {"city":"北京"} } 调用天气API 返回原始数据 { "temperature": "22°C", "condition": "晴" } “北京今天晴天,气温22°C” User AI_Model MCP_Server External_Service

3. 代码实现mcp客户端和服务端

现在python编写mcp server和mcp client的有两个分别是FastMCPMCP,其中MCP是官方的pythonsdk,这两个之间的关系是官方收编了FastMCP的第一个版本的包,但官方集成的是 fastmcp 的 v1.0 版本。然而,jlowin 继续开发 fastmcp,还发布了 v2.0 版本,其中包含代理和客户端采样等新功能。以下的演示以官方版本MCP为例,

安装:uv add "mcp[cli]” 或者pip install "mcp[cli]”

(1) MCP 服务端

from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base

# mcp = FastMCP(name="demo",host="127.0.0.1",port=8256,sse_path="/sse")   ### 启动方式为sse时使用
mcp = FastMCP()
@mcp.tool()
def add_2_numbers(a: int, b: int) -> int:
 """两个数字相加"""
 return a + b

@mcp.resource("config://app")
def get_config() -> str:
    """Static configuration data"""
    return "App configuration here"

@mcp.prompt()
def debug_error(error: str) -> list[base.Message]:
    return [
        base.UserMessage("I'm seeing this error:"),
        base.UserMessage(error),
        base.AssistantMessage("I'll help debug that. What have you tried so far?"),

@mcp.tool()
def multiply_2_numbers(a: int, b: int):
 """两个数字相乘"""
 return a * b

if __name__ == "__main__":
 # mcp.run(transport='sse')   ## 启动方式为sse
 mcp.run(transport='stdio')   ## 启动方式为stdio

解释:

  • Tools(工具)是MCP中最常用的功能之一,它允许LLM执行特定的操作或函数。使用@mcp.tool()装饰器可以轻松将Python函数转换为LLM可调用的工具:
  • Resources(资源)用于向LLM提供数据和上下文信息。与工具不同,资源主要用于读取数据而非执行操作
  • Prompts(提示)允许您创建可重用的提示模板,这些模板可以被参数化并用于标准化LLM交互

简单验证服务端功能可以通过mcp dev server.py进入界面检测

(2) MCP 客户端

MCP客户端一般分别按照服务端的stdio和sse分别写了两个,具体融合的最后修改一下即可。

  1. STDIO客户端
import asyncio
import json
import re
from contextlib import AsyncExitStack
from typing import Optional

from lxml import etree
from mcp import ClientSession, StdioServerParameters, stdio_client
from mcp.client.sse import sse_client
from openai import AsyncOpenAI

class Stdio_MCPClient():

    def __init__(self,api_key, base_url, model):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
        self.model = model
        self.message = []
        with open("MCP_Prompt.txt", "r", encoding="utf-8") as f:
            self.system_prompt = f.read()

    async def connect_to_stdio_server(self, mcp_name, command,args,env={}):
        server_params = StdioServerParameters(
            command=command,
            args=args,
            env=env
        )
        stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio,self.write = stdio_transport
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
        await self.session.initialize()
        response = await self.session.list_tools()
        tools = response.tools
        print(f"成功链接到{mcp_name}服务,对应的tools:",[tool.name for tool in tools])
        self.available_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema
            }
        } for tool in response.tools]

    async def process_query(self, query: str, stream: bool = False):
        # self.message.append({"role": "system", "content": self.system_prompt})
        self.message.append({"role": "user", "content": query})
        response = await self.client.chat.completions.create(
            model=self.model,
            messages=self.message,
            tools=self.available_tools
        )
        final_text = []
        assistant_message = response.choices[0].message
        while assistant_message.tool_calls:
            for tool_call in assistant_message.tool_calls:
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)

                result = await self.session.call_tool(tool_name, tool_args)
                print(f"calling tools {tool_name},wirh args {tool_args}")
                print("Result:", result.content[0].text)
                self.message.extend([
                    {
                        "role": "assistant",
                        "content": None,
                        "tool_calls": [tool_call]
                    },
                    {
                        "role": "tool",
                        "content": result.content[0].text,
                        "tool_call_id": tool_call.id
                    }
                ])
            response = await self.client.chat.completions.create(
                model=self.model,
                messages=self.message,
                tools=self.available_tools,
                max_tokens=8048

            )
            assistant_message = response.choices[0].message
        content = assistant_message.content
        final_text.append(content)

        return "\n".join(final_text)

    async def chat_loop(self,stream_mode=True):
        self.message = []
        while True:
            try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break
                if query.strip() == '':
                    continue
                response = await self.process_query(query, stream=stream_mode)
                print("\nAI:", response)
            except Exception as e:
                print(f"\nError: {str(e)}")

    async def cleanup(self):
        await self.exit_stack.aclose()

async def main():
    with open("config.json", "r") as f:
        config = json.load(f)
    client = Stdio_MCPClient(config["llm"]["api_key"], config["llm"]["base_url"], config["llm"]["model"])
    try:
        env = {}
        await client.connect_to_stdio_server("testserver","python",["server.py",],{})
        await client.chat_loop()
    except  Exception as e:
        print(e)
    finally:
        await client.cleanup()

if __name__ == '__main__':
    asyncio.run(main())
  1. SSE客户端
import asyncio
import json
import re
from contextlib import AsyncExitStack
from typing import Optional

from lxml import etree
from mcp import ClientSession, StdioServerParameters, stdio_client
from mcp.client.sse import sse_client
from openai import AsyncOpenAI

class SSE_MCPClient():

    def __init__(self,api_key, base_url, model):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
        self.model = model
        self.message = []
        with open("MCP_Prompt.txt", "r", encoding="utf-8") as f:
            self.system_prompt = f.read()

       async def connect_to_sse_server(self, mcp_name, server_url,headers=None):
        self.service = [{
            "name": mcp_name,
            "url": server_url,
            "headers": headers
        }]
        sse_transport = await self.exit_stack.enter_async_context(sse_client(server_url, headers,timeout=30,sse_read_timeout=30))
        self.sse,self.write = sse_transport
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.sse, self.write))
        await self.session.initialize()
        response = await self.session.list_tools()
        tools = response.tools
        print(f"成功链接到{mcp_name}服务,对应的tools:",[tool.name for tool in tools])
        self.available_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema
            }
        } for tool in response.tools]

    async def reconnect_sse_server(self):
        for service in self.service:
            mcp_name = service["name"]
            server_url = service["url"]
            headers = service.get("headers", None)
            sse_transport = await self.exit_stack.enter_async_context(sse_client(server_url, headers))
            self.sse, self.write = sse_transport
            self.session = await self.exit_stack.enter_async_context(ClientSession(self.sse, self.write))
            await self.session.initialize()
            print(f"重新成功链接到 {mcp_name} 服务")
            
    async def process_query(self, query: str, stream: bool = False):
        # self.message.append({"role": "system", "content": self.system_prompt})
        self.message.append({"role": "user", "content": query})
        response = await self.client.chat.completions.create(
            model=self.model,
            messages=self.message,
            tools=self.available_tools
        )
        final_text = []
        assistant_message = response.choices[0].message
        while assistant_message.tool_calls:
            for tool_call in assistant_message.tool_calls:
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)
								await self.reconnect_sse_server()
                result = await self.session.call_tool(tool_name, tool_args)
                print(f"calling tools {tool_name},wirh args {tool_args}")
                print("Result:", result.content[0].text)
                self.message.extend([
                    {
                        "role": "assistant",
                        "content": None,
                        "tool_calls": [tool_call]
                    },
                    {
                        "role": "tool",
                        "content": result.content[0].text,
                        "tool_call_id": tool_call.id
                    }
                ])
            response = await self.client.chat.completions.create(
                model=self.model,
                messages=self.message,
                tools=self.available_tools,
                max_tokens=8048

            )
            assistant_message = response.choices[0].message
        content = assistant_message.content
        final_text.append(content)

        return "\n".join(final_text)

    async def chat_loop(self,stream_mode=True):
        self.message = []
        while True:
            try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break
                if query.strip() == '':
                    continue
                response = await self.process_query(query, stream=stream_mode)
                print("\nAI:", response)
            except Exception as e:
                print(f"\nError: {str(e)}")

    async def cleanup(self):
        await self.exit_stack.aclose()

async def main():
    with open("config.json", "r") as f:
        config = json.load(f)
    client = Stdio_MCPClient(config["llm"]["api_key"], config["llm"]["base_url"], config["llm"]["model"])
    try:
       await client.connect_to_sse_server(mcp_name="test", server_url="https://127.0.0.1:7860/sse")
        await client.chat_loop()
    except  Exception as e:
        print(e)
    finally:
        await client.cleanup()

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

注意: sse链接,我增加了一个reconnect_sse_server 函数,主要原因是sse链接过程中过2分钟会自然断开,不论什么办法都无法处理,因此增加这样一个操作。

(3)版本的自然更新

有了上面两种客户端的连接方法,自然而然结合两个就可以做到同时结合sse和stdio的方法只需要增加一个分别调用的方法即可,后续代码微微改动便可使用。

当然官方的MCP也是在不段更新的,看了官方有发布Streamable HTTP Transport ,这种方式在取代sse,以及通过with来启动执行服务的更新等等,一些简单的更新参考下面,更多更新可以前往github上看

from mcp.server.fastmcp import FastMCP

# Stateful server (maintains session state)
mcp = FastMCP("StatefulServer")

# Stateless server (no session persistence)
mcp = FastMCP("StatelessServer", stateless_http=True)

# Stateless server (no session persistence, no sse stream with supported client)
mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True)

# Run server with streamable_http transport
mcp.run(transport="streamable-http")

其余高级用法可参考页面:https://github.com/modelcontextprotocol/python-sdk#advanced-usage

三、典型应用场景

1. MCP 适用场景

  • 企业系统整合

    将 CRM/ERP 封装为 MCP 服务,供多个 Agent 安全调用

    # MCP 连接数据库示例
    @app.post("/mcp")
    def query_database(request: dict):
        if request["function"] == "get_user_orders":
            user_id = request["parameters"]["user_id"]
            # 执行SQL查询 (伪代码)
            return {"orders": db.query(f"SELECT * FROM orders WHERE user_id={user_id}")}
    
  • 跨平台自动化

    组合 GitHub + Slack 的 MCP 服务实现 CI/CD 流程:

    # 自动化工作流:提交代码→构建→通知
    def ci_cd_pipeline():
        call_mcp("github", {"action": "pull_code", "repo": "my-app"})
        build_result = call_mcp("jenkins", {"job": "build"})
        call_mcp("slack", {"channel": "dev-team", "message": f"构建结果:{build_result}"})
    

2. Function Call 适用场景

# 简单实时查询(无需MCP)
def get_stock_price(symbol: str):
    return yahoo_finance_api(symbol)

# 注册函数到模型
functions = [{
    "name": "get_stock_price",
    "parameters": {"symbol": {"type": "string"}}
}]

# 模型直接调用
response = model.generate("AAPL当前股价?", functions=functions)
if response.function_call:
    print(get_stock_price(response.function_call.arguments["symbol"]))

3. 传统 API 调用

# 直接调用 REST API(无AI参与)
import requests
def fetch_weather(city: str):
    response = requests.get(f"https://api.weather.com/v1/{city}")
    return response.json()["temperature"]

四、技术选型建议

场景推荐方案原因
简单同步任务(天气/股价查询)Function Call低延迟,与模型紧密集成
跨系统异步任务(数据分析流水线)MCP标准化协议支持复杂工作流
企业内部系统暴露服务MCP统一认证 + 访问控制
第三方公共服务调用API + Function Call无需额外协议层

关键结论:MCP 的核心价值在于建立企业级 AI 基础设施。当系统需要连接多个异构数据源、要求严格的协议标准化或涉及长周期任务时,MCP 是优于 Function Call 的选择。

🌟 如果您对前沿科技、人工智能,尤其是多模态语言模型的应用前景充满好奇,那么这里就是您获取最新资讯、深入解析的绝佳平台。我们不仅分享创新技术,还探讨它们如何塑造我们的未来。

🔍 想要不错过任何一篇精彩内容,就请订阅我们的公众号吧!您的关注是我们持续探索和分享的动力。在这里,我们一起揭开AI的神秘面纱,见证科技如何让世界变得更加精彩。

### 关于MCP客户端的信息 MCP(Machine Communication Protocol)是一种基于客户端-服务器架构的通信协议。其核心功能在于通过标准化的数据交互方式,在客户端服务之间建立高效、灵活的连接[^1]。 #### MCP客户端的功能描述 MCP客户端作为嵌入式组件,通常被集成至人工智能应用程序中。它的主要职责是向MCP服务器发送请求并接收响应数据。这些请求可以涉及多种操作,例如获取特定数据源的内容、调用API接口或执行预定义的任务。例如,某些实现可能依赖JavaScript编译后的脚本文件来处理参数传递和逻辑控制[^2]。 #### 如何下载或使用MCP客户端? 目前并没有公开的标准分发渠道用于直接下载通用版本的MCP客户端;然而,开发者可以根据需求自行开发定制化解决方案。以下是几种常见的实践方法: 1. **自建客户端** 开发者可以选择合适的编程语言和技术栈来自行设计MCP客户端。例如,有案例展示了如何运用C#结合Avalonia框架构建跨平台图形界面的应用程序[^3]。这种方法允许高度个性化配置以满足具体的业务场景要求。 2. **参考现有项目** 如果存在开源社区贡献的相关资源,则可以从GitHub等平台上查找类似的实现方案作为起点。注意审查目标项目的许可证条款确保兼容性后再加以利用。 3. **安装依赖项** 对于部分已经打包好的软件包而言(如果有的话),遵循官方文档指引完成必要的环境准备步骤之后即可运行实例。比如上述提到的一个例子需要用到Node.js runtime支持下的`.js`文件[`D:/Learning/AI-related/fetch-mcp/dist/index.js`]来进行初始化设置。 #### 技术选型建议 对于希望快速上手又兼顾多操作系统适配性的团队来说,采用.NET生态内的技术如C#/Avalonia组合不失为明智之举因为它们提供了丰富的UI控件库以及良好的性能表现同时还能简化部署流程减少维护成本。 ```csharp // 示例代码片段展示了一个简单的MCP客户端启动过程 using System; namespace McpClientApp { class Program{ static void Main(string[] args){ Console.WriteLine("Starting MCP Client..."); string mcpServerAddress = "http://localhost:8080"; // 假设的服务地址 try{ var clientInstance = new MpcClient(mcpServerAddress); bool isConnected = await clientInstance.ConnectAsync(); if(isConnected){ Console.WriteLine($"Successfully connected to {mcpServerAddress}"); // 发送测试消息给服务器 var responseMessage = await clientInstance.SendMessageAsync("Ping"); Console.WriteLine($"Received from server:{responseMessage}"); } }catch(Exception ex){ Console.Error.WriteLine($"Error occurred while connecting:{ex.Message}"); } } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值