【MCP】详细了解MCP协议:和function call的区别何在?如何使用MCP?

本文介绍了MCP大模型上下文协议的的概念,并对比了MCP协议和function call的区别,同时用python sdk为例介绍了mcp的使用方式。

1. 什么是MCP?

官网:https://modelcontextprotocol.io/introduction

2025年,Anthropic提出了MCP协议。MCP全称为Model Context Protocol,翻译过来是大模型上下文协议。这个协议的主要为AI大模型和外部工具(比如让AI去查询信息,或者让AI操作本地文件)之间的交互提供了一个统一的处理协议。我们常用的USB TypeC接口(USB-C)统一了USB接口的样式,MCP协议就好比AI大模型中的USB-C,统一了大模型与工具的对接方式。

MCP协议采用了C/S架构,也就是服务端、客户端架构,能支持在客户端设备上调用远程Server提供的服务,同时也支持stdio流式传输模式,也就是在客户端本地启动mcp服务端。只需要在配置文件中新增MCP服务端,就能用上这个MCP服务器提供的各种工具,大大提高了大模型使用外部工具的便捷性。

image.png

MCP是开源协议,能让所有AI厂商、AI工具都将MCP集成到自己的客户端中,从而扩大MCP的可用面。毕竟只有用的人越多,协议才能不断发展,不断变得更好。

2. 了解function call

在MCP没有出来之前,我们的AI Agent开发如果想调用外部工具需要针对不同的AI大模型SDK编写不同的代码,其中最为常用的是openai提供的function call的处理逻辑。

本小节参考博客:

2.1. function call demo

2.1.1. 配置工具,AI提供参数

当我们调用 OpenAI Chat Completions 接口时,可以通过tools参数传入可供使用的外部工具。这个工具的调用中就包含了工具的作用,工具需要传入的参数,以及参数的释义。其中tool_choice字段设置为auto代表让大模型自动选择tools,设置为none时不会调用外部工具。

{
  "tool_choice": "auto",
  "messages": [
    {
      "role": "system",
      "content": "你是一个天气查询助手"
    },
    {
      "role": "user",
      "content": "帮我查询上海的天气"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "获取指定城市的天气",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "城市名"
            }
          },
          "required": ["city"],
        }
      }
    }
  ]
}

对应的python openai代码如下,我们将tools部分放入一个包含dict的list,作为create函数的tools参数即可。同时tool_choice传入auto代表自动选择工具。这里我用了硅基流动提供的Qwen2.5模型作为演示,运行下面这个代码需要修改api_key为正确值。

import openai # 1.75.0
import json # 后续会用到json

def main():
    client = openai.OpenAI(
        api_key="xxxxx",
        base_url="https://api.siliconflow.cn/v1")
    tools = [{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名"
                    }
                },
                "required": ["city"],
            }
        }
    }]
    res = client.chat.completions.create(model="Qwen/Qwen2.5-32B-Instruct",
                                         messages=[{
                                             "role": "system",
                                             "content": "你是一个天气查询助手"
                                         }, {
                                             "role": "user",
                                             "content": "帮我查询上海的天气"
                                         }],
                                         tools=tools,
                                         tool_choice="auto")
    print("content:",res.choices[0].message.content)
    print("tools:",res.choices[0].message.tool_calls)
    print("message:", res.choices[0].message.to_dict())

运行程序,发出请求后,大模型就会根据用户提出的问题和提供的tools,来为这个tools编写需要提供的参数。此时content会是空,不会输出内容,tool_calls中会包含调用的工具和参数。

❯ uv run main.py
content: 
tools: [ChatCompletionMessageToolCall(id='01964be6e485603d6a2a0acbbc7eba91', function=Function(arguments='{"city": "上海"}', name='get_weather'), type='function')]
message: {'content': '', 'role': 'assistant', 'tool_calls': [{'id': '01964be6e485603d6a2a0acbbc7eba91', 'function': {'arguments': '{"city": "上海"}', 'name': 'get_weather'}, 'type': 'function'}]}

对应如下json格式响应,包含了我们的参数

  "message": {
        "role": "assistant",
        "content": "",
        "tool_calls": [
          {
            "id": "01964be6e485603d6a2a0acbbc7eba91",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\n  \"city\": \"上海\"\n}"
            }
          }
        ]
      }
2.1.2. 调用工具并让AI二次处理

随后,我们就可以根据这个大模型返回的参数来调用我们的函数,并得到函数的返回结果,再次与大模型进行对话。此时需要按下面的方式维护对话上下文,首先需要将第一次请求AI返回的结果插入到上下文中("role": "assistant"的json字符串),然后再插入工具调用的数据,格式如下:

{
	"role": "tool",
	"content": "工具调用结果",
	"tool_call_id": "ai调用工具时返回的id"
}

其中content代表工具调用的结果(字符串形式,内容可以是json),并且需要用tool_call_id来标识这是哪一个工具调用的请求,必须要和"role": "assistant"响应中的id对应。

二次AI交互对应python代码如下,在上文提供的python代码之后追加

    # 插入结果,再次对话
    messages.append(res.choices[0].message.to_dict()) # ai第一次返回的数据
    # 工具调用的参数
    tool_call = res.choices[0].message.tool_calls[0]
    arguments = json.loads(tool_call.function.arguments)
    messages.append({
        "role": "tool",
        "content": get_weather(arguments['city']),
        "tool_call_id": tool_call.id
    })
    # 二次请求
    res = client.chat.completions.create(model=model,
                                         messages=messages,
                                         tools=tools,
                                         tool_choice="auto")
    print("content:", res.choices[0].message.content)
    print("tools:", res.choices[0].message.tool_calls)
    print("message:", res.choices[0].message.to_dict())

其中get_weather函数如下,为了测试是写死的值,返回一个json字符串

def get_weather(location):
    return '{"Celsius": 27, "type": "sunny"}'

最终运行结果,AI成功根据我们工具调用的返回值来输出了对话方式的天气情况,包括温度和晴天。这样我们就完成了一个完整的tools调用和AI再处理的过程了

❯ uv run main.py
content: 
tools: [ChatCompletionMessageToolCall(id='01964be6e485603d6a2a0acbbc7eba91', function=Function(arguments='{"city": "上海"}', name='get_weather'), type='function')]
message: {'content': '', 'role': 'assistant', 'tool_calls': [{'id': '01964be6e485603d6a2a0acbbc7eba91', 'function': {'arguments': '{"city": "上海"}', 'name': 'get_weather'}, 'type': 'function'}]}
-------------
content: 上海当前的天气是晴天,温度是27摄氏度。
tools: None
message: {'content': '上海当前的天气是晴天,温度是27摄氏度。', 'role': 'assistant'}

{% hideToggle 本次function call的完整上下文和代码 %}

完整json上下文信息

[
    {
        "role": "system",
        "content": "你是一个天气查询助手"
    },
    {
        "role": "user",
        "content": "帮我查询上海的天气"
    },
    {
        "content": "",
        "role": "assistant",
        "tool_calls": [
            {
                "id": "01964beeb9ee27098b74149d86560b35",
                "function": {
                    "arguments": "{\"city\": \"上海\"}",
                    "name": "get_weather"
                },
                "type": "function"
            }
        ]
    },
    {
        "role": "tool",
        "content": "{\"Celsius\": 27, \"type\": \"sunny\"}",
        "tool_call_id": "01964beeb9ee27098b74149d86560b35"
    },
    {
	    "role": "assistant",
	    "content": "上海当前的天气是晴天,温度是27摄氏度。"
    }
]

完整代码如下

import openai # 1.75.0
import json


def get_weather(location):
    return '{"Celsius": 27, "type": "sunny"}'


def main(model="Qwen/Qwen2.5-32B-Instruct"):
    client = openai.OpenAI(
        api_key="xxxx",
        base_url="https://api.siliconflow.cn/v1")
    tools = [{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名"
                    }
                },
                "required": ["city"],
            }
        }
    }]
    messages = [{
        "role": "system",
        "content": "你是一个天气查询助手"
    }, {
        "role": "user",
        "content": "帮我查询上海的天气"
    }]
    res = client.chat.completions.create(model=model,
                                         messages=messages,
                                         tools=tools,
                                         tool_choice="auto")
    print("content:", res.choices[0].message.content)
    print("tools:", res.choices[0].message.tool_calls)
    print("message:", res.choices[0].message.to_dict())
    print("-------------------")

    # 插入结果,再次对话
    messages.append(res.choices[0].message.to_dict())
    tool_call = res.choices[0].message.tool_calls[0]
    arguments = json.loads(tool_call.function.arguments)
    messages.append({
        "role": "tool",
        "content": get_weather(arguments['city']),
        "tool_call_id": tool_call.id
    })
    res = client.chat.completions.create(model=model,
                                         messages=messages,
                                         tools=tools,
                                         tool_choice="auto")
    print("content:", res.choices[0].message.content)
    print("tools:", res.choices[0].message.tool_calls)
    print("message:", res.choices[0].message.to_dict())


if __name__ == "__main__":
    main()

{% endhideToggle %}

2.1.3. tool_choice设置为none

这里也测试一下把tool_choice设置为none的情况,此时即便传入了tools,AI也不会认为有外部工具,会直接返回文字说明。tool_choice还有另外一个可选值是required,也就是必须要调用外部工具。

❯ uv run main.py
content: 我无法提供实时数据或即时查询服务,因为我当前的功能不包括访问互联网获取最新信息。你可以通过查询各类天气应用查看上海最新的天气情况,或者提供具体日期,我可以教你如何根据这些信息来判断和理解天气状况。如果你有任何关于天气的一般性问题,或需要了解某些天气条件的影响,也欢迎向我询问。
tools: None
message: {'content': '我无法提供实时数据或即时查询服务,因为我当前的功能不包括访问互联网获取最新信息。你可以通过查询各类天气应用查看上海最新的天气情况,或者提供具体日期,我可以教你如何根据这些信息来判断和理解天气状况。如果你有任何关于天气的一般性问题,或需要了解某些天气条件的影响,也欢迎向我询问。', 'role': 'assistant'}

2.2. function call的问题

如下是一次function call的流程图

graph TD
	A[编写tools,并提供tools参数配置] --> |调用OpenAI Chat Completions接口,提供tools|C[AI理解tools的作用和参数,并返回调用参数]
	C --> |脚本处理AI返回的参数,调用tools,获取结果|E[AI获取tools结果,解析并生成自然语言回答]

经过这个流程会发现一个问题,即便是简单的调用一个只有单参数的获取天气的函数,在使用openai这个第三方库的情况下都需要费很大劲,主要是tools的调用操作需要我们自己编写脚本实现,如果我们整个AI处理流程涉及到更多tools函数的时候,就很难处理了。

为了解决这个问题,openai在2025年新开源的OpenAI Agent SDK中提供了更加便捷的tools工具调用的处理,只需要编写一个工具类,在Agent初始化的时候传入,Agent就能自动识别这个工具类中的方法并调用工具(自动给本地的函数传入参数)。示例代码可以参考开源仓库中的example

不过这还是没有解决一个最根本的问题,那就是外部工具调用方式的不统一。光是我现在知道的Agent开发SDK就有三个(OpenAI、QwenAgent、谷歌ADK),这三个SDK都会有一套自己的tools调用逻辑,而且openai和qwen的SDK更是只支持自家的模型,这样就会导致即便是同一个tools,在使用不同的SDK的时候,也需要针对这个SDK去重新编写一套tools的调用逻辑,很是麻烦。

了解了这个背景之后,想必你能理解MCP协议的重要性了,它规范了tools的调用方式,同一个tools我们只需要编写一次MCP Server,就能够在众多支持mcp的AI Agent里面被调用,解决了针对不同AI模型或SDK对tools进行单独适配的痛点!

3. MCP协议详解

接下来让我们简单了解一下MCP协议是怎么提供统一的tools调用方式的。在这之前,需要先注明几个名词

  • MCP Hosts:如 Claude Desktop、IDE 或 AI 工具,希望通过 MCP 访问数据的程序(也就是AI Agent程序)
  • MCP Clients:用于维护和服务器链接的一对一工具
  • MCP Servers:通过MCP协议提供特定能力
  • 本地数据源:MCP服务器可以安装访问本地的文件、数据库和服务
  • 远程服务:MCP服务器可通过各类API链接互联网上的外部系统

其中要注意MCP Hosts和Clients的区别,为了更好的区分,后文会用AI来指代MCP Hosts,毕竟MCP工具的输出结果都是会让AI来处理的。

3.1. MCP Server

3.1.1. tools

以官方的MCP Server Demo为例,在tools模式下,Server主要提供的是两个能力,一个是获取当前服务器支持的tools,另外一个就是call tool调用工具的能力。

其中,获取当前支持的tools会返回tools列表、每个tools的inputSchema参数和参数的type/description释义。这一点和function call是类似的,只不过mcp的sdk将其包装成了一个types.Tool类

@app.list_tools()
async def list_tools() -> list[types.Tool]:
	return [
		types.Tool(
			name="fetch",
			description="Fetches a website and returns its content",
			inputSchema={
				"type": "object",
				"required": ["url"],
				"properties": {
					"url": {
						"type": "string",
						"description": "URL to fetch",
					}
				},
			},
		)
	]

除了人工编写这部分参数列表,我们还可以参考mcp_server_git的实现,借助pydantic来定义每一个方法的参数列表,并使用schema()自动获取参数释义。

Tool(
	name=GitTools.DIFF_STAGED,
	description="Shows changes that are staged for commit",
	inputSchema=GitDiffStaged.schema(),
)

举个例子,运行下面的代码,可以得到GitStatus这个类定义的两个入参的名称、类型和说明

from pydantic import BaseModel

class GitStatus(BaseModel):
    repo_path: str
    hello_text: str

# 运行会提示schema函数已经弃用,建议使用model_json_schema替代
print(GitStatus.model_json_schema())
# 输出
# {'properties': {'repo_path': {'title': 'Repo Path', 'type': 'string'}, 'hello_text': {'title': 'Hello Text', 'type': 'string'}}, 'required': ['repo_path', 'hello_text'], 'title': 'GitStatus', 'type': 'object'}

需要注意的是,这里获取到的title释义是直接根据参数名称来的,并没有人工编写的description那么准确。所以使用这种方式传入inputSchema的时候,需要我们尽可能地标准命名参数名称,让AI能通过参数名称直接推断出这个参数要传入什么内容

调用函数的操作就和function call类似了,MCP协议传入的同样也是arguments列表,需要我们将其解析并调用我们实际编写的函数

@app.call_tool()
async def fetch_tool(
	name: str, arguments: dict
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
	if name != "fetch":
		raise ValueError(f"Unknown tool: {name}")
	if "url" not in arguments:
		raise ValueError("Missing required argument 'url'")
	return await fetch_website(arguments["url"]) # 调用实际函数

除了这种复杂的方式,mcp sdk还提供了一个FastMcp,只需要我们在编写的函数上加一个@mcp.tool()装饰器,就能立马把我们的普通函数变成mcp tools,非常方便。

from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

mcp.run() # 运行server

使用这种方式对时候,装饰器会自动去获取我们函数的参数以及参数的类型,并生成types.Tool返回给客户端。这个装饰器有两个参数,name在不传入的时候默认为函数名称,description在不传入的时候默认为函数的docstring(也就是函数下的"""注释)

name: Optional name for the tool (defaults to function name)
description: Optional description of what the tool does

从SDK的源码中可以找到,装饰器会调用Tool.from_function来生成types.Tool类型

    def add_tool(
        self,
        fn: Callable[..., Any],
        name: str | None = None,
        description: str | None = None,
    ) -> Tool:
        """Add a tool to the server."""
        tool = Tool.from_function(fn, name=name, description=description)
        existing = self._tools.get(tool.name) # 判断是否有同名函数已经被注册过了
        if existing:
            if self.warn_on_duplicate_tools:
                logger.warning(f"Tool already exists: {tool.name}")
            return existing # 直接返回已注册的同名函数,不返回本次新注册的函数
        # 新注册函数
        self._tools[tool.name] = tool
        return tool

Tool.from_function函数中会注册name和description

func_name = name or fn.__name__
if func_name == "<lambda>": # lambda表达式没有函数名,必须传入name参数
	raise ValueError("You must provide a name for lambda functions")
func_doc = description or fn.__doc__ or ""
3.1.2. prompts和resources

除了最常用的tools,mcp还提供了prompts和resources两种服务方式,其中prompt是用于定义一些常用操作的提示词,此时客户端可以直接去获取这些提示词和ai交互,避免我们针对某一个流程重复编写提示词;resources是定义一个url格式,当我们的交流中出现这个url格式的时候,ai就可以调用这个工具去做一些特定操作,比如请求某个api或者操作数据库。

但很不幸的是,以上都是慕雪的个人简单理解,由于prompt和resources实在没有找到可以参考的博客或如何使用的demo,我并不是很理解它们在AI工具中是怎么被使用以及是在什么时候被使用的。网上针对MCP的教程也主要集中于tools层面。

后续如果对这俩有更多了解了,再回来补充本文。

3.2. 客户端配置本地和远程MCP Server

在MCP SDK中主要提供了两种server启动的方式,一个是stdio流式传输(本地)的方式,另外一个是sse远程API的方式。

TRANSPORTS = Literal["stdio", "sse"]

这两种方式分别对应了两种服务器的配置方式。如果是本地的mcp服务器,需要使用命令来指定mcp服务器代码文件所在路径,并启动它。这个代码可以是github上克隆的仓库,也可以是通过npm或其他方式安装到本地的可执行文件。

3.2.1. 本地(stdio)

以python编写的MCP Server为例,需要通过如下方式启动某一个MCP Server,其中--directory指定的工作路径,必须指定这个工作路径才能找到mcp-simple-tool的代码

# 官方 mcp-simple-tool demo
uv run --directory /Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool mcp-simple-tool

此时是采用stdio方式启动的server,对应配置文件如下(可供Agent SDK调用)

{
	"mcpServers": {
		"mcp-simple-tool": {
			"command": "uv",
			"args": [
				"run",
				"--directory",
				"/Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool",
				"mcp-simple-tool"
			]
		}
	}
}

当客户端需要使用这个MCP Server的时候,会自动根据我们配置的命令去尝试在本地启动这个mcp服务端,然后和它交互。所以,如果使用stdio来配置MCP Server但本地却没有uv环境的时候,程序是无法启动的。

以CherryStudio为例,在mcp配置中,以stdio格式添加我们这个配置,点击右上角保存,保存成功则代表配置正常。保存失败则需要检查配置的命令和路径是否出错

image.png

此时勾选底部的MCP服务器,和AI对话,给出一个URL,他会自动调用工具去下载这个URL的html文件,并解析和输出他对这个HTML文件的理解。

image.png

需要注意的是CherryStudio的mcpServers json配置文件并不是标准mcpServers的格式,多了一些字段,估计是方便前端设计。在AI的初次输出中也会把GdTGt4qMFpnyYqBxaqTrM输出出来,因为在标准mcpServers配置中GdTGt4qMFpnyYqBxaqTrM字段就是mcp服务端的名称。

{
  "mcpServers": {
    "GdTGt4qMFpnyYqBxaqTrM": {
      "isActive": true,
      "name": "网页获取",
      "type": "stdio",
      "description": "通过url获取网页内容",
      "registryUrl": "",
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool",
        "mcp-simple-tool"
      ]
    }
  }
}

标准的mcpServers的格式并不需要那些额外字段,只需要我在前文给出的基础mspServers配置就可以了。以Qwen-Agent为例,只需要把json填入tools中就可以调用mcp服务器了。

from qwen_agent.agents import Assistant

def init_agent_service():
    llm_cfg = {
        'model': 'Qwen/Qwen2.5-32B-Instruct',
        'model_server': 'https://api.siliconflow.cn/v1',
        'api_key': 'xxxx'
    }
    system = ('你是一个强大的助手,可以帮用户处理问题。')
    tools = [{
        "mcpServers": {
            "mcp-simple-tool": {
                "command":
                "uv",
                "args": [
                    "run", "--directory",
                    "/Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool",
                    "mcp-simple-tool"
                ]
            }
        }
    }]
    bot = Assistant(
        llm=llm_cfg,
        name='网页查看助手',
        description='网页查看',
        system_message=system,
        function_list=tools,
    )

    return bot


def main(text='这个网站是什么?https://blog.musnow.top/'):
    bot = init_agent_service()
    messages = [{'role': 'user', 'content': text}]
    # 请求
    for response in bot.run(messages):
        print('bot response:', response)


if __name__ == "__main__":
    main()

Qwen最终的输出如下

{'role': 'assistant', 'content': '这个网址是一个个人博客站点,名字叫做“慕雪的寒舍”。站点描述自己为“爱折腾的代码初学者”。\n\n博客主要内容包含了编程学习(如Python、C、ROS等)、博客建站的相关知识以及一些编程相关的项目开发记录。\n\n网站首页还展示了近期发表的文章、公告、分类和标签等信息方便用户查找和浏览。\n\n总之,这是一个包含了编程学习和项目开发记录内容的个人技术博客。', 'reasoning_content': '', 'name': '网页查看助手'}
3.2.2. 远程(sse)

远程调用的配置就需要服务器的url了。首先通过如下方式启动demo,提供命令行参数sse以远程方式启动,port指定端口8000

uv run --directory /Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool mcp-simple-tool --transport sse --port 8000

此时终端会输出当前进程PID以及服务端的url

INFO:     Started server process [17058]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

对应mcpServers配置文件,其中disabled是当前server是否被禁用,设置false为启用这个server,timeout是链接服务端的超时时间。

{
  "mcpServers": {
    "exampleServer": {
      "url": "http://127.0.0.1:8000/sse",
      "disabled": false,
      "timeout": 30
    }
  }
}

在cherrystudio中填写 http://0.0.0.0:8000/sse作为sse服务地址

image.png

同样可以正常调用,输出结果

image.png

注意,在QwenAgent SDK中必须使用http://127.0.0.1:8000/sse才能正常连接这个远程服务器,不能使用http://0.0.0.0:8000/sse,否则会出现502 Bad Gateway错误,详见issue

INFO:mcp.client.sse:Connecting to SSE endpoint: http://0.0.0.0:8000/sse/
DEBUG:httpcore.connection:connect_tcp.started host='127.0.0.1' port=7897 local_address=None timeout=5 socket_options=None
DEBUG:httpcore.connection:connect_tcp.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x1075a3c70>
DEBUG:httpcore.http11:send_request_headers.started request=<Request [b'GET']>
DEBUG:httpcore.http11:send_request_headers.complete
DEBUG:httpcore.http11:send_request_body.started request=<Request [b'GET']>
DEBUG:httpcore.http11:send_request_body.complete
DEBUG:httpcore.http11:receive_response_headers.started request=<Request [b'GET']>
DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 502, b'Bad Gateway', [(b'Connection', b'close'), (b'Content-Length', b'0')])
INFO:httpx:HTTP Request: GET http://0.0.0.0:8000/sse/ "HTTP/1.1 502 Bad Gateway"
DEBUG:httpcore.http11:response_closed.started
DEBUG:httpcore.http11:response_closed.complete
2025-04-19 15:51:29,240 - mcp_manager.py - 206 - INFO - Failed to connect to server: unhandled errors in a TaskGroup (1 sub-exception)
INFO:qwen_agent_logger:Failed to connect to server: unhandled errors in a TaskGroup (1 sub-exception)
2025-04-19 15:51:29,247 - mcp_manager.py - 91 - INFO - Error executing function: 'NoneType' object is not iterable
INFO:qwen_agent_logger:Error executing function: 'NoneType' object is not iterable

修改了tools的配置为远程url,其他代码保持不变

from qwen_agent.agents import Assistant
import logging
logging.basicConfig(level=logging.DEBUG)

def init_agent_service():
    llm_cfg = {
        'model': 'Qwen/Qwen2.5-32B-Instruct',
        'model_server': 'https://api.siliconflow.cn/v1',
        'api_key': 'xxxx'
    }
    system = ('你是一个强大的助手,可以帮用户处理问题。')
    tools = [{
        "mcpServers": {
            "exmaple-server": {
                "url": "http://127.0.0.1:8000/sse",
                "disabled": False,
                "timeout": 30
            }
        }
    }]
    bot = Assistant(
        llm=llm_cfg,
        name='网页查看助手',
        description='网页查看',
        system_message=system,
        function_list=tools,
    )

    return bot


def main(text='这个网站是什么?https://blog.musnow.top/'):
    bot = init_agent_service()
    messages = [{'role': 'user', 'content': text}]
    # 请求
    for response in bot.run(messages):
        print('bot response:', response)


if __name__ == "__main__":
    main()

QwenAgent正常调用工具并返回网页的结果

{'role': 'assistant', 'content': '这是一个名为"慕雪的寒舍"的博客网站。博客的作者是慕雪年华。网站上有很多关于编程学习、技术分享的文章,同时,博客还提供了分类、标签、归档等功能来帮助读者查找信息。它看起来像是一个个人的技术博客。', 'reasoning_content': '', 'name': '网页查看助手'}

image.png

3.3. 使用MCP协议后的tools调用流程图

使用MCP协议后,流程图就变成了下面这样,此时ai工具就从tools中解放了出来,我们可以随心所欲地添加任何我们需要的MCP Server配置,最终ai能自动调用这些外部工具并处理他们的结果,不再需要像function call一样人工编写脚本处理大模型生成的参数,也不需要在不同Agent SDK中独立为tools编写适配代码了。

graph TD;
	A[ai工具加载mcp配置] --> |启动和连接mcp服务器|B
	B[mcp client] --> |链接服务器,获取已有工具,调用工具|C[MCP Server] 
	C -->|返回已有工具,返回工具调用结果|B

在让AI调用外部工具的方面,MCP协议还是非常重要的。

不过需要注意的是,MCP和function call并不是完全独立的关系。目前MCP Host中接入MCP工具有两种方式,一个是prompt的方式,另外一个就是function call方式。也就是说,在部分ai工具中,会直接使用function call功能来让ai识别MCP工具并调用。MCP主要的助力在于统一了调用这些外部工具的配置项和服务端的实现方式,让我们编写的外部工具能做到一次编写多处使用。

5. The end

关于MCP协议的介绍到这里就结束啦,主要介绍的还是MCP TOOLS方面的内容,有任何问题欢迎评论区讨论。

本文首发地址:【MCP】详细了解MCP协议:和function call的区别何在?如何使用MCP?,首发于 2025-04-19 08:56:01

点我查看更多精彩内容:慕雪的寒舍

<think>嗯,用户问的是“functioncallmcp”的区别。首先,我需要明确这两个术语的具体含义,因为不同的领域可能有不同的解释。根据常见的计算机科学编程上下文,可能的解释方向有两个:操作系统中的系统调用(system call)与进程间通信(IPC)中的消息传递机制,或者是在某些特定框架或库中的概念,比如函数调用(function call)与某种消息传递协议MCP)。 首先,考虑“function call”通常指的是程序中的函数调用,即一个函数执行时调用另一个函数的过程,涉及栈帧、参数传递、返回地址等。这是编程中的基础概念。而“MCP”可能有多种含义,比如“Message Channel Protocol”、“Microprocessor Control Protocol”,或者是某些系统中的专有术语。如果用户指的是操作系统中的机制,可能需要考虑“Message Passing”相关的概念,比如在进程间通信中的消息传递(Message Passing)与函数调用的对比。 需要进一步验证可能的领域。例如,在分布式系统中,消息传递(Message Passing)是进程间通信的一种方式,而本地函数调用则是同步的、直接的调用。另一个可能性是,在某些框架或库中,比如gRPC或某些RPC框架,可能会有远程函数调用(类似RPC)特定的消息传递协议(如MCP)的区别。 如果用户指的是系统级的概念,比如系统调用(syscall)中的“function call”与“MCP”,这里可能存在混淆。系统调用是用户空间程序请求内核服务的接口,而MCP可能是指某种消息传递机制,如Mach内核中的消息传递。此时,两者的区别在于系统调用的方式:系统调用通常通过软中断或专用指令触发,而消息传递可能通过共享内存、消息队列等方式实现。 另外,也可能用户提到的“mcp”是指“Message Channel Protocol”或其他特定协议,需要具体分析。例如,在微服务架构中,服务间通信可能通过HTTP/RPC(类似函数调用)或消息队列(如AMQP、Kafka)来实现,后者属于消息传递模式。 综合来看,可能的对比方向是:函数调用(Function Call)作为直接的、同步的调用方式,而消息传递(Message Passing)作为间接的、异步的通信机制。两者的主要区别在于调用方式、通信模型(同步/异步)、耦合度、错误处理机制等。 需要进一步确认用户的具体上下文,但由于无法询问,只能基于常见情况解释。因此,在回答中需要明确假设可能的定义,并对比两者的区别,包括执行方式、通信机制、适用场景等,同时指出可能的歧义,建议用户提供更多上下文以便更准确的解答。</think>### Function CallMCP区别分析 需要明确的是,**“Function Call”** **“MCP”** 的具体含义可能因上下文不同而变化。以下是两种常见场景下的解释: --- #### **1. 编程与系统设计中的对比** 假设 **Function Call** 指常规函数调用,**MCP** 指消息传递协议(Message Passing Protocol)或类似机制: | **特性** | **Function Call** | **MCP(消息传递)** | |------------------|-------------------------------------|----------------------------------| | **执行方式** | 同步调用,直接控制权转移 | 异步通信,通过发送/接收消息完成 | | **通信范围** | 同一进程内(或线程内) | 跨进程、跨机器、分布式系统 | | **耦合性** | 高(需明确调用目标) | 低(通过消息队列/主题解耦) | | **错误处理** | 通过异常或返回值直接处理 | 需监听反馈或超时机制 | | **典型应用** | 函数库、本地逻辑执行 | 微服务通信、事件驱动架构 | **示例** - **Function Call**(函数调用): ```python def add(a, b): return a + b result = add(3, 5) # 同步调用,直接返回结果 ``` - **MCP**(消息传递): 生产者发送消息到 Kafka 主题,消费者异步订阅处理。 --- #### **2. 操作系统中的对比** 若 **Function Call** 指系统调用(System Call),**MCP** 指消息控制协议(Message Control Protocol): | **特性** | **System Call** | **MCP(消息控制协议)** | |------------------|-------------------------------------|----------------------------------| | **目的** | 用户程序请求内核服务(如文件操作) | 管理进程间通信或硬件消息传递 | | **触发方式** | 软中断(如 `int 0x80`) | 通过特定硬件/协议通道 | | **性能开销** | 较高(需上下文切换) | 依赖实现(可能优化为低延迟) | | **典型场景** | 打开文件、网络通信 | 驱动层通信、实时系统消息传递 | **示例** - **System Call**: ```c int fd = open("file.txt", O_RDONLY); // 调用内核打开文件 ``` - **MCP**: 嵌入式系统中,通过 CAN 总线协议传递控制消息。 --- ### 关键区别总结 1. **执行模型** - Function Call:同步、直接、紧耦合。 - MCP:异步、间接、松耦合。 2. **适用场景** - Function Call:单机程序逻辑、快速本地操作。 - MCP:分布式系统、解耦组件、高容错需求。 3. **技术实现** - Function Call 依赖栈寄存器传递参数。 - MCP 依赖队列、网络协议或专用硬件通道。 --- ### 注意事项 - **术语歧义**:若 MCP 指代其他特定协议(如 **Multicast Communication Protocol**),需结合具体领域重新分析。 - **建议**:提供更多上下文(如操作系统、框架名称),可进一步明确差异。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕雪华年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值