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

MCP-与本地大模型集成实现工具调用

这里结合网络资料,尝试结合MCP(Model Context Protocol)与大型语言模型实现工具调用,以一个简单的应用来展示这一过程。

1 创建mcp虚拟环境

conda create -n mcp python=3.12

conda activate mcp

pip install uv

2 安装必要依赖包

pip install "mcp[cli]"

pip install openai langchain langchain-mcp-adapters langgraph langchain_ollama -U

3 安装ollama模型工具

linux ollama安装大致和mac安装类似,大致参考

在mac m1基于ollama运行deepseek r1_mac m1 ollama-CSDN博客

受限于硬件环境,这里使用qwen的1.5b小模型

ollama run qwen2.5:1.5b

4 mcp服务端

score_points.txt,记录成绩数据,辅助server.py代码实现简单应用

小明:
    语文:80
    数学:95
小红:
    语文:100
    数学:100

mcp服务代码server.py

from mcp.server.fastmcp import FastMCP
import osmcp = FastMCP("Tom's tools")@mcp.tool()
def check_child_study_situation(name: str) -> str:"""检查小孩最近的学习状况"""db = {"小明": "学习很努力 from Michael阿明老师点评","小红": "学习一般","小刚": "学习不太好",}print(f"Checking study status for {name}")return db.get(name, "没有找到这个小孩的学习记录")@mcp.tool()
def query_student_scores(name: str) -> str:"""查询学生成绩Args:name: 学生姓名Returns:学生成绩信息,如果未找到则返回相应提示"""file_path = os.path.join(os.path.dirname(__file__), "score_points.txt")try:with open(file_path, "r", encoding="utf-8") as f:content = f.read()# 解析文件内容students = {}current_student = Nonefor line in content.split("\n"):line = line.strip()if not line:continueif line.endswith(":"):  # 学生名current_student = line[:-1]  # 去掉结尾的冒号students[current_student] = {}elif current_student and ":" in line:  # 科目分数subject, score = line.split(":")students[current_student][subject.strip()] = score.strip()# 返回学生成绩if name in students:result = f"{name}的成绩:\n"for subject, score in students[name].items():result += f"- {subject}: {score}\n"return resultelse:return f"没有找到{name}的成绩记录"except Exception as e:return f"查询成绩出错: {str(e)}"@mcp.tool()
def add(a: int, b: int) -> int:"""Add two numbers"""return a + b@mcp.tool()
def multiply(a: int, b: int) -> int:"""Multiply two numbers"""return a * bif __name__ == "__main__":mcp.run(transport="stdio")

5 mcp客户端

通过加载 MCP 工具并创建反应式代理(react agent)来处理用户请求。

以下是客户端代码示例client.py

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
from langchain_ollama import ChatOllama
import asynciomodel = ChatOllama(model='qwen2.5:1.5b')server_params = StdioServerParameters(command="python",args=["server.py"],
)async def run_agent():async with stdio_client(server_params) as (read, write):async with ClientSession(read, write) as session:await session.initialize()tools = await load_mcp_tools(session)agent = create_react_agent(model, tools)agent_response = await agent.ainvoke({"messages": [{"role": "user", "content": "小明最近的学习状态怎么样?"},]})return agent_responseif __name__ == "__main__": result = asyncio.run(run_agent())messages = result["messages"]print(len(messages))print(messages)

这是llm选择qwen2.5:1.5b时的回复,可见llm并没有真实理解咱们的意图。

[

*HumanMessage(content='小明最近的学习状态怎么样?', additional_kwargs={}, response_metadata={}, id='e66611e3-d9de-42a9-8676-d4982ccae318'),

---

* AIMessage(content='好的,请问您想了解小明的具体学习状况吗?我可以帮您检查一下。请提供他的姓名,我会马上进行查询。\n请问小明的姓名是什么?', additional_kwargs={}, response_metadata={'model': 'qwen2.5:1.5b', 'created_at': '2025-08-03T12:48:52.573277334Z', 'done': True, 'done_reason': 'stop', 'total_duration': 11749528145, 'load_duration': 65738997, 'prompt_eval_count': 356, 'prompt_eval_duration': 476507137, 'eval_count': 37, 'eval_duration': 11201131239, 'model_name': 'qwen2.5:1.5b'}, id='run--e51372c2-6909-4bbf-a681-64511c3f8986-0', usage_metadata={'input_tokens': 356, 'output_tokens': 37, 'total_tokens': 393})]

我们换一个能力更强但相对也比较小的模型qwen3:8b,重新运行后,结果如下。

这次就比较符合预期,所以当mcp结果不太理想时,可尝试换个当前硬件能支持的更强的llm,有可能就获得符合预期的结果了。

[* HumanMessage(content='小明最近的学习状态怎么样?', additional_kwargs={}, response_metadata={}, id='7d10dce4-ffb4-4fff-bb6e-2d6d20def146'),

---

* AIMessage(content='<think>\n好的,用户问的是小明最近的学习状态怎么样。我需要先看看有哪些可用的工具。提供的工具里有一个check_child_study_situation函数,描述是检查小孩最近的学习状况,参数需要名字。另一个是query_student_scores,查询成绩,参数也是名字。还有加法和乘法函数,应该不相关。\n\n用户的问题是关于学习状态,不是成绩,所以可能更适合用check_child_study_situation。不过也有可能用户想通过成绩来了解学习状态,这时候可能需要用query_student_scores。但问题中没有提到成绩,所以优先考虑第一个函数。需要确认参数是否正确,参数是name,用户提到了小明,所以参数应该是name: "小明"。应该调用check_child_study_situation函数,参数是小明。这样更直接回应学习状态的问题。如果用户后续需要成绩,再调用另一个函数。现在先调用检查学习状况的函数。\n</think>\n\n', additional_kwargs={}, response_metadata={'model': 'qwen3:8b', 'created_at': '2025-08-03T13:48:40.403280213Z', 'done': True, 'done_reason': 'stop', 'total_duration': 753502645412, 'load_duration': 3622735922, 'prompt_eval_count': 340, 'prompt_eval_duration': 426867643736, 'eval_count': 224, 'eval_duration': 323011540155, 'model_name': 'qwen3:8b'}, id='run--afb88487-3c4b-4b57-9bbf-01d2616bf88c-0', tool_calls=[{'name': 'check_child_study_situation', 'args': {'name': '小明'}, 'id': '5ec3bd6f-07c4-4687-b598-5912944fb5c4', 'type': 'tool_call'}], usage_metadata={'input_tokens': 340, 'output_tokens': 224, 'total_tokens': 564}),

---

*ToolMessage(content='学习很努力 from Michael阿明老师点评', name='check_child_study_situation', id='90591982-6fd6-4655-a14e-b60a11aedc94', tool_call_id='5ec3bd6f-07c4-4687-b598-5912944fb5c4'),
---

*AIMessage(content='<think>\n</think>\n\n小明最近的学习状态非常好,学习非常努力,老师也给予了积极的评价。如果还有其他想了解的情况,可以继续提问哦!', additional_kwargs={}, response_metadata={'model': 'qwen3:8b', 'created_at': '2025-08-03T13:49:58.626358151Z', 'done': True, 'done_reason': 'stop', 'total_duration': 78211764828, 'load_duration': 42962753, 'prompt_eval_count': 562, 'prompt_eval_duration': 28885173128, 'eval_count': 35, 'eval_duration': 49264293169, 'model_name': 'qwen3:8b'}, id='run--b2cd78ab-e6a6-4c09-84ff-79ba541c70ab-0', usage_metadata={'input_tokens': 562, 'output_tokens': 35, 'total_tokens': 597})]

6 mcp的工具和调用

实现LLM与外部交互时,tool 是具体的工具实现,function calling 是调用这些工具的方式。

tool,定义在 MCP 服务器上的功能模块,通过 MCP 协议暴露给客户端,具有封装性、可发现性和异步性特点。
Function calling,LLM调用外部函数的能力,增强了模型的能力,使其能够借助外部资源解决问题。增强模型能力、动态交互、参数传递与结果处理等。

MCP 是协议规范,定义了 LLM 和工具之间的通信方式;function calling 是LLM的能力,利用 MCP 协议调用tool。这很好理解,但FastMCP在没明确路由情况下,如何知道何时基于function calling调用具体工具。FastMCP在通过@mcp.tool() 装饰器注册函数为工具时,会自动获取函数的参数信息、文档信息、注释字符串等信息,LLM依据这些信息判断何时调用@mcp.tool()注册的函数,具体过程参考附录。

附录

@mcp.tool() 装饰器用于将函数自动注册为当前 mcp 服务器中工具。

摘选之前的片段:

@mcp.tool()
def get_weather(city: str) -> str:
    """获取指定城市的天气信息"""
    # 简单模拟数据,实际应用中应该调用对应的API
    weather_data = {
        "北京": "晴天,温度 22°C",
        "上海": "多云,温度 25°C", 
        "广州": "小雨,温度 28°C",
        "深圳": "阴天,温度 26°C"
    }
    return weather_data.get(city, f"{city} 的天气数据暂不可用")

这段代码实际上会:

1)自动提取函数的参数类型信息、文档字符串。

============================================================
  开始解析函数: get_weather
   文档字符串: 获取指定城市的天气信息
============================================================

函数签名分析:
   完整签名: (city: str) -> str
   返回类型: <class 'str'>
   参数数量: 1
参数名: city
    原始注解: <class 'str'>
    参数种类: POSITIONAL_OR_KEYWORD
    默认值: <class 'inspect._empty'>
    类型化注解: <class 'str'>
    字段信息: annotation=<class 'str'>, default=PydanticUndefined

2. 生成参数的 JSON Schema(函数名+Arguments)。

创建 Pydantic 模型:
模型名称: get_weatherArguments
基类: <class 'mcp.server.fastmcp.utilities.func_metadata.ArgModelBase'>
模型创建成功: <class '__main__.get_weatherArguments'>

get_weatherArguments JSON Schema:
{
  "properties": {
    "city": {
      "title": "City",
      "type": "string"
    }
  },
  "required": [
    "city"
  ],
  "title": "get_weatherArguments",
  "type": "object"
}

3. 将函数注册为 MCP 工具(self._tools[tool.name] = tool)。

mcp.tool() 可以接受以下参数(此处参数解释参考 tool 和 func_metadata):

name: 可选的工具名称,默认为函数名

title: 可选的工具标题(用于人类阅读)

description: 可选的工具功能描述,默认使用函数的文档字符串

annotations: 可选的 ToolAnnotations,提供额外的工具信息

structured_output:控制工具输出是结构化还是非结构化的

  • None: 基于函数的返回类型注解自动检测
  • True: 无条件创建结构化工具(在返回类型注解允许的情况下)。如果是结构化,会根据函数的返回类型注释创建 Pydantic 模型。支持各种返回类型:
    • BaseModel 子类(直接使用)
    • 原始类型(str、int、float、bool、bytes、None)- 包装在带有 'result' 字段的模型中
    • TypedDict - 转换为具有相同字段的 Pydantic 模型
    • 数据类和其他带注释的类 - 转换为 Pydantic 模型
    • 泛型类型(list、dict、Union 等)- 包装在带有 'result' 字段的模型中
  • False: 无条件创建非结构化工具

reference

---

深入 FastMCP 源码:认识 tool()、resource() 和 prompt() 装饰器

https://zhuanlan.zhihu.com/p/1932083718639563855

fastmcp

https://github.com/modelcontextprotocol/python-sdk/tree/main/src/mcp/server/fastmcp

mcp, model context protocol

https://github.com/modelcontextprotocol/python-sdk

如何实现本地大模型与MCP集成
https://www.cnblogs.com/smartloli/p/18905572

基于 MCP 协议的 LLM 工具调用

https://blog.csdn.net/qq_21201267/article/details/147344774

http://www.dtcms.com/a/312899.html

相关文章:

  • 微服务的使用
  • java中Optional类的使用和注意采坑
  • DBMS设计 之2 从数据中台到三种中台
  • 常见的框架漏洞(Thinkphp,spring,Shiro)
  • 常见的框架漏洞
  • IO流-对象流
  • MCP革命:AI世界的“USB-C”接口如何重塑智能体与外部工具的连接
  • 均线:从市场脉搏到量子计算的时空密码
  • K8S几种常见CNI深入比较
  • Qt::AA_DontCreateNativeWidgetSiblings使用注意事项
  • 游戏设计原理
  • Flutter开发 dart异步
  • Linux网络编程 ---五种IO模型
  • 基于 Spring Boot + Vue 实现人脸采集功能全流程
  • Python----大模型(从预训练到分布式优化的核心技术解析)
  • 2、RabbitMQ的5种模式基本使用(Maven项目)
  • 迈向透明人工智能: 可解释性大语言模型研究综述
  • ubuntu apt安装与dpkg安装相互之间的关系
  • Python 实例属性与方法命名冲突:一次隐藏的Bug引发的思考
  • 途游Android面试题及参考答案
  • 【GitHub探索】Agent开发平台CozeStudio开源版本踩坑体验
  • pycharm上如何添加conda环境
  • 嵌入式 C 语言入门:多文件编程实践笔记 —— 从文件创建到调用
  • 为何:内存数据断电即逝,硬盘数据牢笼长存
  • LangChain框架概念及简单的使用案例
  • ABP VNext + CloudEvents:事件驱动微服务互操作性
  • 计算机核心概念辨析与解析
  • 24SpringCloud黑马商城部署Java应用后浏览器访问数据库不显示数据的解决办法
  • 可持久化线段树 系列 题解
  • 【Python✨】解决 Conda 安装 MoviePy 报错问题