萬物互聯的開始: Funcation Call 及 MCP 協議解析筆記 -一-

MCP(Model Context Protocol,模型上下文協議) ,2024 年 11 月底,由 Anthropic 推出的一種開放標準,旨在統一大型語言模型(LLM)與外部數據源和工具之間的通信協議。MCP 的主要目的在於解決當前 AI 模型因數據孤島限制而無法充分發揮潛力的難題,MCP 使得 AI 應用能夠安全地訪問和操作本地及遠程數據,爲 AI 應用提供了連接萬物的接口。

MCP 和 function call 的區別

Function Call 指的是 AI 模型根據上下文自動執行函數的機制,充當了 AI 模型與外部系統之間的橋樑,不同的模型有不同的 Function Calling 實現,代碼集成的方式也不一樣。由不同的 AI 模型平臺來定義和實現。
目前支持 function 的模型有 openai 的 GPT 系列,阿里的 qwen,智源的 GLM 系列

Model Context Protocol (MCP)
MCP 是一個標準協議,如同電子設備的 Type C 協議 (可以充電也可以傳輸數據),使 AI 模型能夠與不同的 API 和數據源無縫交互。
MCP 旨在替換碎片化的 Agent 代碼集成,從而使 AI 系統更可靠,更有效。通過建立通用標準,服務商可以基於協議來推出它們自己服務的 AI 能力,從而支持開發者更快的構建更強大的 AI 應用。開發者也不需要重複造輪子,通過開源項目可以建立強大的 AI Agent 生態。
MCP 可以在不同的應用 / 服務之間保持上下文,從而增強整體自主執行任務的能力。
由於 MCP 是一種協議,各個模型在使用這套協議下都能接入

MCP 終極指南: https://guangzhengli.com/blog/zh/model-context-protocol/

function call 使用方法

function call 一般是和 langchain 搭配着使用
llm 調用可以參考下面這個流程圖

這邊使用一個 demo 來講述 ollama 調用方式:

from ollama import ChatResponse, chat
import ollama
import logging
import requests

defget_current_weather(location, unit="celsius"):
    """
    Fetches the current weather for a given location.

    Args:
    location (str): The city and country, e.g., "San Francisco, USA"
    unit (str): Temperature unit, either "celsius" or "fahrenheit"

    Returns:
    str: A string describing the current weather, or an error message
    """
    logging.info(f"Getting weather for {location}")
    base_url = "https://api.open-meteo.com/v1/forecast"

    # Set up parameters for the weather API
    params = {
        "latitude": 0,
        "longitude": 0,
        "current_weather""true",
        "temperature_unit": unit
    }

    # Set up geocoding to convert location name to coordinates
    geocoding_url = "https://geocoding-api.open-meteo.com/v1/search"
    location_parts = location.split(',')
    city = location_parts[0].strip()
    country = location_parts[1].strip() iflen(location_parts) > 1else""

    geo_params = {
        "name": city,
        "count": 1,
        "language""en",
        "format""json"
    }

    try:
        # First attempt to get coordinates
        logging.info(f"Fetching coordinates for {location}")
        geo_response = requests.get(geocoding_url, params=geo_params)
        geo_response.raise_for_status()
        geo_data = geo_response.json()
        logging.debug(f"Geocoding response: {geo_data}")

        # If first attempt fails, try with full location string
        if"results"notin geo_data ornot geo_data["results"]:
            geo_params["name"] = location
            geo_response = requests.get(geocoding_url, params=geo_params)
            geo_response.raise_for_status()
            geo_data = geo_response.json()
            logging.debug(f"Second geocoding attempt response: {geo_data}")

        # Extract coordinates if found
        if"results"in geo_data and geo_data["results"]:
            params["latitude"] = geo_data["results"][0]["latitude"]
            params["longitude"] = geo_data["results"][0]["longitude"]
            logging.info(
                f"Coordinates found: {params['latitude']}, {params['longitude']}")
        else:
            logging.warning(f"No results found for location: {location}")
            returnf"Sorry, I couldn't find the location: {location}"

        # Fetch weather data using coordinates
        logging.info("Fetching weather data")
        response = requests.get(base_url, params=params)
        response.raise_for_status()
        weather_data = response.json()
        logging.debug(f"Weather data response: {weather_data}")

        # Extract and format weather information
        if"current_weather"in weather_data:
            current_weather = weather_data["current_weather"]
            temp = current_weather["temperature"]
            wind_speed = current_weather["windspeed"]

            result = f"The current weather in {location} is {temp}°{unit.upper()} with a wind speed of {wind_speed} km/h."
            logging.info(f"Weather result: {result}")
            return result
        else:
            logging.warning(f"No current weather data found for {location}")
            returnf"Sorry, I couldn't retrieve weather data for {location}"
    except requests.exceptions.RequestException as e:
        logging.error(f"Error occurred while fetching weather data: {str(e)}")
        returnf"An error occurred while fetching weather data: {str(e)}"

weather_tool = {
    'type''function',
    'function': {
        'name' : 'get_current_weather',
        'description' : 'Get the current weather in a given location"',
        "parameters": {
            "type""object",
            "properties": {
                "location": {
                    "type""string",
                    "description""The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type""string",
                    "enum": ["celsius""fahrenheit"],
                },
            },
        'required': ['location'],
        }
  }
}



messages = [{'role''user''content''What’s the weather like in Nanjing?'}]
print('Prompt:', messages[0]['content'])

available_functions = {
    'get_current_weather': get_current_weather
}

client= ollama.Client(host=f"http://127.0.0.1:11434")

response: ChatResponse = client.chat(
'qwen2.5:14b',
  messages=messages,
    tools=[weather_tool],

)

if response.message.tool_calls:
# There may be multiple tool calls in the response
for tool in response.message.tool_calls:
    # Ensure the function is available, and then call it
    if function_to_call := available_functions.get(tool.function.name):
      print('Calling function:', tool.function.name)
      print('Arguments:', tool.function.arguments)
      output = function_to_call(**tool.function.arguments)
      print('Function output:', output)
    else:
      print('Function', tool.function.name, 'not found')

demo 的整體邏輯如下圖

demo 分爲幾部分

    1. tool
get_current_weather

爲我們提供給大模型的工具內容爲根據提供的地址 通過接口查到對應地點的溫度信息

    1. 工具描述
weather_tool = {
    'type': 'function',
    'function': {
        'name' : 'get_current_weather',
        'description' : 'Get the current weather in a given location"',
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                },
            },
        'required': ['location'],
        }
  }
}

這個是對於工具的描述 主要包含了 方法的類型,方法的名字描述,以及出入參數的描述,這個文件是大模型能否更好的調用模型的關鍵,比如 去掉 工具需要的參數

weather_tool = {
    'type': 'function',
    'function': {
        'name' : 'get_current_weather',
        'description' : 'The city and state, e.g. San Francisco, CA'
  }
}

會發現 雖然模型也解析出了我們 我們需要一個 地點 但工具需要的入參的 key 並不是工具所需要,需要更加精準的提示


所以這個也可以成爲 我們寫工具提示比較好的模板 後面寫其他函數也可以按照這個
更加近一步 可以把工具類代輸入給大模型 然後模型 給我們按我們給的提示模板自動生成 tool 留個坑 之後來試一下 , 估計這種適合 qwen2.5-coder 模型效果會更加好一些

    1. ollama 請求
response: ChatResponse = client.chat(
  'qwen2.5:14b',
  messages=messages,
    tools=[weather_tool],
)

ollama 去年 7 月後的版本開始支持 tool 的調用:https://ollama.com/blog/tool-support
通過將工具的描述傳給工具的方式使用描述傳給 ollama 我們可以通過抓包獲取 ollama 的請求方式和結果


可以看到在傳給 ollama 時帶上了工具類的描述,然後再 在模型的結果中給出了調用的 tool 然後也給分離出了入參數
4. 模型調用工具的方式
這部分就是代碼上的能力了找到對應的 tool 的入口然後將調用函數

引用:
LangChain 函數調用: https://wangwei1237.github.io/LLM_in_Action/langchain_function_call.html

MCP 使用方法

mcp 是 claude 公司提出的模型調用工具的統一協議,旨在統一大型語言模型(LLM)與外部數據源和工具之間的通信協議,當下各個工具

mcp 的架構


從官方給出的架構圖看主要分爲幾部分
MCP Hosts: 像 Claude 桌面、IDE 或希望通過 MCP 訪問數據的 AI 工具
MCP Clients: 與服務器保持 1:1 連接的協議客戶端
MCP Servers: 每個通過標準化的模型上下文協議公開特定功能的輕量級程序
Local Data Sources:MCP 服務器可以安全訪問 您的計算機文件、數據庫和服務,
Remote Services: MCP 服務器可通過互聯網(例如,通過 API)訪問的外部系統
可以看到 MCP 的分層架構很清晰
官方文檔地址: https://modelcontextprotocol.io/introduction#general-architecture

由於官方文檔給出的例子都是基於 claude desktop 的 但 claude 的價格實在高 再加上賬號還被封了,其他也有比如可以使用 vscode+cline+deepseek+mcp 來實現 mcp 調用 但這終究時工具使用上,還是想看一下正常 mcp 交互的整個流程 比如使用 client 使用 ollama
後續比如能通過代碼這個最直接的方式來使用這套規則

ollama+mcp 實戰

這邊找到了一個比較基礎的 mcphost 名字就是這麼直接 ,地址 https://github.com/mark3labs/mcphost
這個老哥是使用 go 實現了 mcphost

先得有基礎的環境 這邊建議使用 Ubuntu+go1.23+nodejs+ollama
通過老哥給的步驟:

安裝go
go install github.com/mark3labs/mcphost@latest
ollama拉模型
ollama pull qwen2.5:7b

然後加上老哥給的一個配置文件

{
  "mcpServers": {
    "sqlite": {
      "command": "uvx",
      "args": [
        "mcp-server-sqlite",
        "--db-path",
        "/tmp/foo.db"
      ]
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/tmp"
      ]
    }
  }
}

這個配置主要描述了 模型可以使用的 mcp 服務其中 uvx 和 npx 分別是 python 的 uv 管理方式 npx 爲快速執行 nodejs 腳本的
然後
mcp-server-sqlite 地址: https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite
server-filesystem:https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem

mcphost.exe -m ollama:qwen2.5:7b --config t2.json

然後運行一下就可以看到 qwen 執行了本地的提示的操作 nice

如果你是 windows 系統可以看之前的內容,安裝一個 wsl 環境
再也不用切環境了,wsl2 安裝運行 vllm 指南:https://mp.weixin.qq.com/s/GsfSJdchgBcOvHsQkrl5xA

本章主要講了 funcation call ollama 的調用方式 以及一個 ollama 調 mcp 的方法,下一章將詳細介紹 mcphost 這個 demo 以及 mcp 協議

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/vGJ2_hHeL-AILhwROiIvZA