鱼C论坛

 找回密码
 立即注册
查看: 122|回复: 4

[基础知识] MCP快速入门天气查询之客户端示例

[复制链接]
发表于 5 天前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 快速收敛 于 2025-5-7 16:29 编辑

在帖子MCP官方示例+Cherry Studio尝试中,我们创建了MCP官方示例中的天气接口服务,并用软件Cherry Studio用作客户端接入了MCP服务,完成了简单的MCP使用。

接下来我们看下官方通过代码实现MCP客户端来连接MCP服务端。官方代码使用的是Anthropic,由于不能访问国外,使用不了Claude的api,我都换成了通义千问的大模型的api,经过测试也能完成测试。

1. 创建项目
  1. # 创建项目目录
  2. uv init mcp-client
  3. cd mcp-client

  4. # 创建虚拟环境
  5. uv venv

  6. # 激活虚拟环境
  7. # 在 Windows 上:
  8. .venv\Scripts\activate
  9. # 在 Unix 或 MacOS 上:
  10. source .venv/bin/activate

  11. # 安装所需的包
  12. uv add mcp anthropic python-dotenv

  13. # 删除样板文件
  14. rm main.py

  15. # 创建我们的主文件
  16. touch client.py
复制代码


2. 首先需要到阿里百炼大模型平台申请API秘钥,将秘钥保存至项目目录中的.env文件中
7cf60281-6c8e-4972-b75b-e056f3eb13f9.png

3. 阿里的api可以使用openai的sdk,完全兼容,所以只需要安装openai库即可。将官方示例的代码修改为
  1. import asyncio
  2. from typing import Optional
  3. from contextlib import AsyncExitStack

  4. from mcp import ClientSession, StdioServerParameters
  5. from mcp.client.stdio import stdio_client

  6. from openai import OpenAI
  7. from dotenv import load_dotenv
  8. import os
  9. import json

  10. load_dotenv()  # load environment variables from .env

  11. class MCPClient:
  12.     def __init__(self):
  13.         # Initialize session and client objects
  14.         self.session: Optional[ClientSession] = None
  15.         self.exit_stack = AsyncExitStack()
  16.         self.openai = OpenAI(
  17.             api_key=os.getenv("DASHSCOPE_API_KEY"),  # 如果您没有配置环境变量,请用阿里云百炼API Key将本行替换为:api_key="sk-xxx"
  18.             base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 填写DashScope base_url
  19.         )
复制代码



4. 创建链接到MCP服务的方法,这一部分代码不用修改:
  1.     async def connect_to_server(self, server_script_path: str):
  2.         """Connect to an MCP server

  3.         Args:
  4.             server_script_path: Path to the server script (.py or .js)
  5.         """
  6.         is_python = server_script_path.endswith('.py')
  7.         is_js = server_script_path.endswith('.js')
  8.         if not (is_python or is_js):
  9.             raise ValueError("Server script must be a .py or .js file")

  10.         command = "python" if is_python else "node"
  11.         server_params = StdioServerParameters(
  12.             command=command,
  13.             args=[server_script_path],
  14.             env=None
  15.         )

  16.         stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
  17.         self.stdio, self.write = stdio_transport
  18.         self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))

  19.         await self.session.initialize()

  20.         # List available tools
  21.         response = await self.session.list_tools()
  22.         tools = response.tools
  23.         print("\nConnected to server with tools:", [tool.name for tool in tools])
复制代码


5. 调用大模型,查询天气接口,这一部分修改为openai风格,使用千问大模型:
  1.     async def process_query(self, query: str) -> str:
  2.         """Process a query using OpenAI and available tools"""

  3.         messages = [{
  4.             "role": "user",
  5.             "content": query
  6.         }]

  7.         # 获取可用工具列表
  8.         response = await self.session.list_tools()
  9.         available_tools = [{
  10.             "type": "function",
  11.             "function": {
  12.                 "name": tool.name,
  13.                 "description": tool.description,
  14.                 "parameters": tool.inputSchema
  15.             }
  16.         } for tool in response.tools]

  17.         final_text = []

  18.         while True:
  19.             # OpenAI API调用
  20.             response = self.openai.chat.completions.create(
  21.                 model="qwen-plus",
  22.                 messages=messages,
  23.                 tools=available_tools,
  24.                 tool_choice="auto",
  25.                 max_tokens=1000
  26.             )

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

  29.             # 如果没有工具调用则结束
  30.             if not message.tool_calls:
  31.                 break

  32.             # 处理所有工具调用
  33.             tool_responses = []
  34.             for tool_call in message.tool_calls:
  35.                 tool_name = tool_call.function.name
  36.                 tool_args = json.loads(tool_call.function.arguments)

  37.                 # 执行工具调用
  38.                 result = await self.session.call_tool(tool_name, tool_args)
  39.                 final_text.append(f"[Called {tool_name} with args {tool_args}]")

  40.                 # 记录工具响应
  41.                 tool_responses.append({
  42.                     "tool_call_id": tool_call.id,
  43.                     "role": "tool",
  44.                     "name": tool_name,
  45.                     "content": result.content
  46.                 })

  47.             # 将工具响应添加到消息历史
  48.             messages.append({
  49.                 "role": "assistant",
  50.                 "content": message.content,
  51.                 "tool_calls": [
  52.                     {
  53.                         "id": tc.id,
  54.                         "type": "function",
  55.                         "function": {
  56.                             "name": tc.function.name,
  57.                             "arguments": tc.function.arguments
  58.                         }
  59.                     } for tc in message.tool_calls
  60.                 ]
  61.             })
  62.             messages.extend(tool_responses)

  63.         return "\n".join([t for t in final_text if t is not None])
复制代码


6. 聊天交互式循环,这一部分不用修改:
  1.     async def chat_loop(self):
  2.         """Run an interactive chat loop"""
  3.         print("\nMCP Client Started!")
  4.         print("Type your queries or 'quit' to exit.")

  5.         while True:
  6.             query = input("\nQuery: ").strip()

  7.             if query.lower() == 'quit':
  8.                 break

  9.             response = await self.process_query(query)
  10.             print("\n" + response)

  11.     async def cleanup(self):
  12.         """Clean up resources"""
  13.         await self.exit_stack.aclose()
复制代码


7. 运行函数,指定之前的MCP服务的python路径:
  1. async def main():
  2.     if len(sys.argv) < 2:
  3.         print("Usage: python client.py <path_to_server_script>")
  4.         sys.exit(1)

  5.     client = MCPClient()
  6.     try:
  7.         await client.connect_to_server(sys.argv[1])
  8.         await client.chat_loop()
  9.     finally:
  10.         await client.cleanup()

  11. if __name__ == "__main__":
  12.     import sys
  13.     asyncio.run(main())
复制代码



代码完成,开启调试:
运行MCP客户端,并指定服务端
2797fb4c-6ab5-45cc-83da-411758387767.png

程序返回了可调用的MCP工具方法,并让用户输入问题。还是使用之前的问题:请问纽约今天的天气怎么样?
9fb1cb70-5b8b-46c8-b2c3-0f4bd7db47ea.png

可以看见程序又列出了可以使用的工具,千问大模型获取到纽约的经纬度,并通过经纬度调用天气信息,再通过千问大模型总结输入.

有点意思 ,但能干啥?

评分

参与人数 2荣誉 +5 鱼币 +6 贡献 +6 C币 +1 收起 理由
不二如是 + 2 + 3 + 3 + 1 MCP未来热度趋势
慢慢即漫漫 + 3 + 3 + 3 无条件支持楼主!

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层
有点意思
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层
欢迎分享更多MCP项目
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 4 天前 | 显示全部楼层
不二如是 发表于 2025-5-8 08:21
欢迎分享更多MCP项目

老菜鸟,只能入个门
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层
快速收敛 发表于 2025-5-8 08:49
老菜鸟,只能入个门

慢慢来~~积累沉淀
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-5-12 07:16

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表