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

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

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

特性 API Function Call MCP (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,最终给用户进行展示。

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

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

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

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

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

(1) MCP 服务端

python 复制代码
   
    
    
    
  
   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客户端
python 复制代码
   
    
    
    
  
   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客户端
python 复制代码
   
    
    
    
  
   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上看

python 复制代码
   
    
    
    
  
   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的神秘面纱,见证科技如何让世界变得更加精彩。

相关推荐
再学一点就睡2 小时前
🌆 一个人的城市,一群人的代码:前端小白的两个月“渡劫”实录
程序员
小奏技术5 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
suke8 小时前
MinIO社区版"挥刀自宫":Web管理功能全砍,社区信任岌岌可危
后端·程序员·开源
柒崽9 小时前
如何搭建一个MCP服务,然后在Cursor中调用,半小时,彻底掌握MCP
mcp
AI大模型10 小时前
大模型系列炼丹术(五):LLM自回归预训练过程详解
程序员·llm
陈随易11 小时前
Lodash 杀手来了!es-toolkit v1.39.0 已完全兼容4年未更新的 Lodash
前端·后端·程序员
徐呀徐大大11 小时前
TRAE:新一代类VSCODE IDE,开发者的高效利器
程序员
AI大模型11 小时前
大模型系列炼丹术(四):从零开始动手搭建GPT2架构
程序员·llm
京东云开发者12 小时前
JDK从8升级到21的问题集
程序员
FE王同学12 小时前
前端开发交互式命令之inquirer
前端·程序员