当前位置: 首页 > news >正文

玩转MCP

玩转MCP

  • 0.环境
  • 1.自定义stdio交互
    • 1.1.server
    • 1.2.client
    • 1.3.效果
  • 2.自定义sse交互
    • 2.1.server
    • 2.2.client
    • 2.3.效果
  • 3.使用官方文件

mcp火了好一阵了,最近一直在大院干活儿,好不容易抽出时间,赶紧来学习学习。
官方文档, mcp广场可以搜索自己需要的server服务,都是些saas服务,其实就是把各家的server下载到本地然后调用。官方分stdio和sse两种交互模式。我这里都用代码自定义,方便项目应用,代码放到仓库了。需要使用claude,vscode等的可以自行搜索使用方法,我这里不涉及。

0.环境

conda create -n mcp python==3.12
conda activate mcp
pip install mcp openai

1.自定义stdio交互

1.1.server

服务端包括三部分:创建mcp客户端,然后写自己的tool都带上@mcp.tool()注解,最后run(这里确定使用stdio方式还是sse方式)

from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my_server", version="1.0.0")@mcp.tool()
def use_my_camera()-> None:passif __name__ == "__main__":mcp.run(transport='stdio')

仓库代码里我写的四个tool,打开本地相机,向本地写入文件,打开spotify并播放某首歌曲,给我一个leetcode题目,写的比较糙,慎用,需要的可以去下载官网的。
这里我在调用spotify的时候记得要去https://developer.spotify.com/dashboard创建自己的开发者账户,拿到自己的client_id和client_secret,这里redirect url最好要写http://127.0.0.1:8888/callback/

1.2.client

客户端包括三部分:连接大模型(我用ollama,qwen2.5),连接mcp服务器(这里stdio和sse有区别),处理用户请求(将大模型和mcp服务建立连接,这里需要根据自己的模型效果改改逻辑)。

import asyncio
from typing import Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import OpenAI
import json
import sys
class MCPClient:def __init__(self):# 初始化会话和客户端对象self.session: Optional[ClientSession] = None  # 使用会话管理和 API 客户端进行初始化self.exit_stack = AsyncExitStack()  # 使用AsyncExitStack管理资源self.openai_api_key = "ollama" self.base_url = "http://192.168.3.36:11434/v1"  #Ollama 服务器的 IP 地址self.model = "qwen2.5:latest"  # Ollama 模型名称self.client = OpenAI(api_key=self.openai_api_key, base_url=self.base_url)async def connect_to_server(self, server_script_path: str):"""连接到MCP服务器Args:server_script_path (str): 服务器脚本路径(.py 或.js)"""if not server_script_path.endswith(('.py', '.js')):raise ValueError("服务器脚本必须是.py 或.js 文件")command = "python" if server_script_path.endswith('.py') else "node"server_params = StdioServerParameters(command=command,args=[server_script_path],env=None)stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))self.stdio, self.write = stdio_transportself.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.toolsprint("\n已连接到服务器, 可用工具:", [tool.name for tool in tools])async def process_query(self, query: str) -> str:"""处理查询,使用Claude和可用工具Args:query (str): 用户输入的查询Returns:str: 处理后的响应内容"""# 构造消息对象messages = [{"role": "user","content": query}]# 获取可用工具列表response = await self.session.list_tools()# 将工具列表转换为需要的格式available_tools = [{"type": "function","function": {"name": tool.name,"description": tool.description,"input_schema": tool.inputSchema}}for tool in response.tools]# 第一次调用Claude API# print("进入》》》》》》》process_query")response = self.client.chat.completions.create(model=self.model,messages=messages,tools=available_tools)print("response",response)# 获取API响应的内容content = response.choices[0].messagefinal_text = []assistant_message_content = []if content.tool_calls:# 获取工具调用信息tool_call = content.tool_calls[0]tool_name = tool_call.function.nametool_args = json.loads(tool_call.function.arguments)# 调用工具result = await self.session.call_tool(tool_name, tool_args)final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")assistant_message_content.append(content)print(f"\n\n[正在用工具 {tool_name}, 参数为 {tool_args}]\n\n")# 修正消息列表更新逻辑messages.append({"role": "assistant","content": "","tool_calls": [tool_call]})messages.append({"role": "tool","content": result.content[0].text if result.content[0].text else "","name": tool_name,"tool_call_id": tool_call.id})response = self.client.chat.completions.create(model=self.model,messages=messages,tools=available_tools)print("response",response)# 获取API响应的内容content = response.choices[0].messagefinal_text.append(content.content)# 如果没有调用工具,直接返回响应内容else:final_text.append(content.content)assistant_message_content.append(content)return "\n".join(final_text)async def chat_loop(self):"""运行交互式聊天循环"""print("\nMCP 客户端已启动!")print("请输入您的查询或输入 'quit' 退出。")while True:try:query = input("user input:").strip()if query.lower() == 'quit':breakresponse = await self.process_query(query)print("\n" + response)except Exception as e:print(f"\n错误: {str(e)}")async def cleanup(self):"""清理资源"""await self.exit_stack.aclose()async def main():if len(sys.argv) < 2:print("用法: python client.py <path_to_server_script>")sys.exit(1)client = MCPClient()try:await client.connect_to_server(sys.argv[1])await client.chat_loop()finally:await client.cleanup()if __name__ == "__main__":asyncio.run(main())

1.3.效果

运行

python client.py server.py

输入命令进行测试,我的命令(写一封500字告白信到我本地,给我播放一首好日子,我想练一个简单的二分法,打开本地相机)。
还可以使用网页端测试效果

pip install mcp[cli]
mcp dev server.py

在这里插入图片描述

2.自定义sse交互

sse交互与stdio差别很小,这里只写区别

2.1.server

这里初始化mcp的时候给一个端口号,运行的时候使用sse模式,剩下工具的定义都不变。

mcp = FastMCP("my_server", version="1.0.0", port=8000)
@mcp.tool()
if __name__ == "__main__":mcp.run(transport='sse')

2.2.client

client这里就是连接mcp服务的时候与stdio不同,然后在main里面调用的时候传递的就是server启动的url地址

async def connect_to_server(self, url: str):"""连接到基于 HTTP SSE 的 MCP 工具服务器"""sse_transport = await self.exit_stack.enter_async_context(sse_client(url))self.session = await self.exit_stack.enter_async_context(ClientSession(*sse_transport))await self.session.initialize()response = await self.session.list_tools()tools = response.toolsprint("\n已连接到服务器, 可用工具:", [tool.name for tool in tools])
async def main():client = MCPClient()try:await client.connect_to_server('http://0.0.0.0:8000/sse')await client.chat_loop()finally:await client.cleanup()

2.3.效果

启动服务端python server.py
启动客户端python client.py
网页端测试mcp dev server.py
在这里插入图片描述

3.使用官方文件

这里我测试了一个操作键鼠,截屏等操作的ScreenPilot,首先git代码,进入环境。
安装依赖包pip install -r requirements.txt
他的代码是stdio的交互(我没找到sse的),所以client我就用第一个stdio里面的,server就用官方的main文件,所以运行python C:\Users\robot\Desktop\mcp\stdio\client.py C:\Users\robot\Desktop\mcp\ScreenPilot\main.py就可以调用这个服务。
在这里插入图片描述

http://www.xdnf.cn/news/236449.html

相关文章:

  • C# dataGridView分页
  • JMeter WebSocket 压测详细步骤(支持 ws+proto 协议)
  • flutter 专题 五十六 Google 2020开发者大会Flutter专题
  • 驱动车辆诊断测试创新 | 支持诊断测试的模拟器及数据文件转换生成
  • 斯坦福RGA软件 老版本和兼容Windows 11版本可选
  • 在 OpenSearch 中建立有效的混合搜索: 技术和最佳实践
  • PCB设计工艺规范(四)安规要求
  • 变量char2、*char2、pChar3、*pChar3的存储位置
  • 冰冰一号教程网--介绍采用vuepress搭建个人博客
  • CrowdStrike推出新型工具防御恶意AI模型与数据窃取
  • 「Mac畅玩AIGC与多模态10」开发篇06 - 使用自定义翻译插件开发智能体应用
  • ai改写智能助手在线润色,高效产出优质文章!
  • Qt -DFS可视化
  • 乐西高速大凉山1号隧道实现双幅贯通:成都到昭觉9小时变3.5小时
  • 代码随想录打卡|Day31动态规划(最后一块石头的重量2、目标和、一和零)
  • 分割链表题解
  • 2025年“深圳杯”数学建模挑战赛A题-芯片热弹性物理参数估计
  • 记一次 MyBatis 缓存引发的问题
  • 高级测试工程师 的面试题汇总
  • 实验-单总线温度采集与显示(汇编语言与接口技术)
  • 视觉问答大模型速递:Skywork-R1V2-38B
  • 【人工智能】深入探索Python中的自然语言理解:实现实体识别系统
  • 第二部分:赤色的世界
  • 提高设计的综合性能
  • ESP32开发之freeRTOS的信号量
  • 免费在Colab运行Qwen3-0.6B——轻量高性能实战
  • Learning vtkjs之ImplicitBoolean
  • Java大师成长计划之第8天:Java线程基础
  • 树状结构转换工具类
  • 沙箱逃逸-通过题解了解沙箱逃逸