一、初探

在初次接触 MCP 时,很多人都会有一个自然的疑问:

“MCP 是什么?”

首先是概念,在初次接触的时候,首先都会去搜这个是什么含义,MCP明显是一个缩写。

去任何搜索引擎上,都可以很快的搜到这个含义MCP (Model Context Protocol),

不过还不够,因为很难理解这个到底是什么。

一般这个时候我会去先看一下概念,发现更多的是类比usb插口,http协议等等,此时可以得到一个初步的结论,这个是大模型的一个标准,这个时候就会有疑问,这个标准用来做什么呢?再继续搜本上到这里索发现是可以用来调用外部接口的能力,基初步的概念形成了。

“MCP是一个标准,可以让大模型有外部调用的能力,比如搜索,可以得到最新信息”

到了这里,概念其实已经形成了,但是明显不太够,MCP到底是个啥,为啥就可以搜索呢。

我想还是很多人都是云里雾里的,我也一样,迷惑了好久。

二、再探

带着这个问题,我的探索思路是从大模型本身入手,既然和大模型有关,那么我们去从源头出发是不是更容易理解呢。

目前市面上有很多开源大模型,随时都可以下载下来自己跑,我没有很高的GPU,所以下载一些小的参数来测试。

我先是用ollama下载另一个qwen3:8B模型,然后直接run起来。

然后我直接问他,今天上海天气怎么样,直接看效果。

可以看到模型的思索过程,里面有提到他觉得应该去调接口,但是实际上没有接口给他调。

>>> 今天上海天气

Thinking...

好的,用户问今天上海的天气。首先,我需要确认当前的日期和时间,因为天气情况会随时间变化。用户可能是在计划出门,比如上班、上学或者旅游,所以需要准确的实时

信息。接下来,我应该访问可靠的天气预报网站或使用API来获取最新的数据。常用的有中国天气网、AccuWeather或者OpenWeatherMap。考虑到用户可能在中国,使用中国天气网会更合适,因为数据更本地化。

然后,我需要检查是否有最新的天气更新,比如是否有突发的天气变化,比如暴雨、台风或者高温预警。比如,如果今天上海有台风,用户可能需要知道避险建议。另外,用

户可能还想知道温度、湿度、风速、降水概率以及紫外线指数等信息,这些都会影响他们的出行计划。

同时,用户可能没有明确说明是否需要穿衣建议或者是否需要带伞,所以最好在回答中包含这些实用信息。例如,如果温度较低,建议穿外套;如果降水概率高,建议带伞。

另外,如果湿度较高,可以提醒注意防潮。

还需要注意天气预报的准确性,不同来源的数据可能会有差异,所以要确保信息是最新的,并且来自权威渠道。如果遇到数据延迟或错误,应该及时更新或提示用户可能有延

迟。此外,用户可能希望得到简洁明了的回答,所以需要将信息分点列出,便于阅读。

最后,检查是否有遗漏的重要信息,比如空气质量指数(AQI)或者PM2.5浓度,这些对健康也可能有影响。如果用户有特殊需求,比如户外活动或健康问题,可能需要更详细

的建议。总之,确保回答全面、准确,并且实用,帮助用户做出合适的决策。

...done thinking.

此时模型其实给了我回答,截图如下。

MCP 是什么?一次从动手实践出发的探索_大模型

由此基本上可以得出初步结论,大模型是没办法实时查询接口的,我这里直接运行大模型,而不是在直接web界面化去问就是这个目的,因为界面化有可能不是原汁原味的大模型,而是可能加了其他代码处理逻辑。

所以下一步思考,那么大模型到底如何调用外部工具呢?

我先准备了两个能力:

1、大模型接口能力,通过ollama调用

2、天气接口,既然想要天气,那么需要有一个接口来调用

好,此时准备了两个能力,

开始思考:如何做呢?

大模型就是一堆参数,感觉无从下手,没办法调用,所以我把思考内容给了大模型,然后大模型帮我实现了代码,运行后结果如下,已经可以调用接口了:

MCP 是什么?一次从动手实践出发的探索_大模型_02

此时可以探索一下代码内容,当然不需要全部看,只是大概看一下即可。

import requests
import json
import time
from typing import List, Dict
from pypinyin import lazy_pinyin


class OllamaAPIClient:
    def __init__(self, base_url: str = "http://localhost:8080"):
        self.base_url = base_url.rstrip("/")
        self.chat_endpoint = f"{self.base_url}/api/chat"
        self.default_timeout = 120
        self.messages: List[Dict] = []

    def get_weather(self, city: str) -> str:
        """调用心知天气 API,支持中文城市自动转拼音"""
        API_KEY = "xx"  # 替换为你的真实 API Key
        city_pinyin = ''.join(lazy_pinyin(city))
        url = f"https://api.seniverse.com/v3/weather/now.json?key={API_KEY}&location={city_pinyin}&language=zh-Hans&unit=c"

        try:
            resp = requests.get(url, timeout=10)
            resp.raise_for_status()
            data = resp.json()
            result = data["results"][0]
            weather = result["now"]
            name = result["location"]["name"]
            return f"{name}当前天气:{weather['text']},气温:{weather['temperature']}°C"
        except Exception as e:
            return f"【天气获取失败】{str(e)}"

    def get_tools(self):
        return [
            {
                "type": "function",
                "function": {
                    "name": "get_weather",
                    "description": "获取指定城市的当前天气",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "city": {
                                "type": "string",
                                "description": "要查询天气的城市(中文),如 北京、上海"
                            }
                        },
                        "required": ["city"]
                    }
                }
            }
        ]

    def chat_once(self, model: str, user_input: str) -> Dict:
        """进行一次非流式请求,返回完整响应数据"""
        user_input = user_input.strip().replace("\u200b", "")
        self.messages.append({"role": "user", "content": user_input})

        headers = {"Content-Type": "application/json"}
        payload = {
            "model": model,
            "messages": self.messages,
            "tools": self.get_tools(),
            "stream": False
        }

        # ✅ 输出完整 input payload
        print("\n📤 [请求 payload]")
        print(json.dumps(payload, indent=2, ensure_ascii=False))

        try:
            response = requests.post(self.chat_endpoint, headers=headers, json=payload, timeout=self.default_timeout)
            response.raise_for_status()
            data = response.json()

            # ✅ 输出模型完整返回内容
            print("\n📥 [模型原始响应]")
            print(json.dumps(data, indent=2, ensure_ascii=False))

            return data
        except Exception as e:
            raise RuntimeError(f"❌ 模型请求失败:{str(e)}")

    def stream_reply(self, model: str):
        """用流式方式输出模型回复"""
        payload = {
            "model": model,
            "messages": self.messages,
            "tools": self.get_tools(),
            "stream": True
        }
        headers = {"Content-Type": "application/json"}

        print("\n🤖 [最终模型回复]: ", end="")
        try:
            with requests.post(self.chat_endpoint, headers=headers, json=payload, timeout=self.default_timeout, stream=True) as resp:
                resp.raise_for_status()
                for line in resp.iter_lines(decode_unicode=True):
                    if not line.strip():
                        continue
                    data = json.loads(line)
                    content = data.get("message", {}).get("content", "")
                    print(content, end="", flush=True)
                print()
        except Exception as e:
            print(f"\n❌ 获取最终回复失败:{str(e)}")

    def handle_tool(self, model: str, tool_calls: List[Dict]):
        """调用工具函数并继续对话"""
        tool_call = tool_calls[0]
        function_name = tool_call["function"]["name"]
        arguments = tool_call["function"]["arguments"]

        if isinstance(arguments, str):
            arguments = json.loads(arguments)
        tool_call_id = tool_call.get("id", f"tool_call_{int(time.time())}")

        if function_name == "get_weather":
            result = self.get_weather(**arguments)

            # 添加工具调用内容
            self.messages.append({"role": "assistant", "tool_calls": tool_calls})
            self.messages.append({
                "role": "tool",
                "tool_call_id": tool_call_id,
                "name": function_name,
                "content": result
            })

            # 流式回复
            self.stream_reply(model)
        else:
            print(f"⚠️ 不支持的工具函数:{function_name}")


def run_chat_loop():
    print("🤖 输入中文城市问天气,如:今天上海天气,输入 exit 退出\n")
    client = OllamaAPIClient()
    model_name = "qwen3:8b"

    while True:
        try:
            user_input = input("你说:").strip()
            if user_input.lower() in {"exit", "quit"}:
                print("👋 再见。")
                break

            # 第一步:非流式请求(判断是否有工具调用)
            data = client.chat_once(model_name, user_input)
            tool_calls = data.get("message", {}).get("tool_calls", [])

            if tool_calls:
                print("\n🔧 [检测到工具调用]")
                client.handle_tool(model_name, tool_calls)
            else:
                print("\n🤖 [直接回复]:", data.get("message", {}).get("content", ""))

        except Exception as e:
            print(f"❌ 出错:{str(e)}")


if __name__ == "__main__":
    run_chat_loop()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.

首先可以确定的是,不是大模型在调用,而是代码在调用,比如python或其他语言,然后返回给大模型。

想到这里延伸出来另一个问题,大模型怎么知道该有这个接口的,果然了解的越多问题越多。

我想可以从给大模型的input里来探索,虽然我只是输入了一个问题,但是实际上真的是这样吗,我们先来看看,如果是,那么我们定义的函数可能在其他位置注入了,如果不是那么很可能在包在输入问题里。

下面是input:

你说:今天上海天气

 [请求 payload]

{

  "model": "qwen3:8b",

  "messages": [

    {

      "role": "user",

      "content": "今天上海天气"

    }

  ],

  "tools": [

    {

      "type": "function",

      "function": {

        "name": "get_weather",

        "description": "获取指定城市的当前天气",

        "parameters": {

          "type": "object",

          "properties": {

            "city": {

              "type": "string",

              "description": "要查询天气的城市(中文),如 北京、上海"

            }

          },

          "required": [

            "city"

          ]

        }

      }

    }

  ],

  "stream": false

}

于是谜题解开,输入的时候会额外输入内容,把我目前定义好的tools一起给大模型。

这个就是定义的tools

def get_tools(self):

        return [

            {

                "type": "function",

                "function": {

                    "name": "get_weather",

                    "description": "获取指定城市的当前天气",

                    "parameters": {

                        "type": "object",

                        "properties": {

                            "city": {

                                "type": "string",

                                "description": "要查询天气的城市(中文),如 北京、上海"

                            }

                        },

                        "required": ["city"]

                    }

                }

            }

        ]

看着这个输入,更多的问题出现了:

1、首先如果有很多工具呢,每次都输入大量的内容,不是很消耗token吗,我明明只问了一句话,但是你给我输入的可是很多

2、如果每一家的tools定义格式都不一样呢,工作量岂不是很大

第二个问题,由此我想到了MCP,很自然的,MCP需要出现,各家需要统一标准,这样的化我就只需要写一次即可,而不是每家都要写,这也就是所谓的统一标准。

第一个问题,网上也有很多优化方向,不在本次讨论范围内。

到这里就结束了吗,还没有,我在想另一个问题,你说这样定义,难道大模型就能识别了吗,那我随便写是不是也行,我说我叫toolsplug行不行,很明显不行,所以这里牵扯出来另一个问题,大模型怎么识别这些输入,带着问题继续探索,模型想要学习需要什么,需要训练,所以想要支持MCP,其实再需要进行一次训练,比如大量的训练后就可以让模型知道,name是干啥的,description是干啥的,所以我想市面上目前应该不是全部大模型都支持mcp的,这需要一定的推动力。

至此关于MCP初步探索结束,这里没有写MCP怎么写,MCP怎么用,仅仅从MCP到底是什么进行的探索,MCP初步迷雾已经解开,未来会怎么发展?我想:

1、优化MCP消耗大量token的问题

2、继续推送生态发展,需要更多的大模型支持。

当然随着发展也许会有更优秀的工具出来,未来无限可能....