MCP全称Model Content Protocol,模型上下文协议。它是一系列协议,是帮助LLM应用快速获取上下文的协议。这个上下文,是指LLM可以调用的工具、资源和提示词。

MCP是帮助AI应用(MCP Host)快速连接外部系统的开源标准协议,它本质跟Coze或Dify的Agent可搭配工具逻辑是一致的,只不过MCP定义了一套标准协议,建立了一个良好的生态。一方面,工具的开发者可以基于MCP协议开发工具(MCP Server),可以插拔式的适配所有满足MCP的AI应用。另一方面,AI应用无需像Coze或者Dify定义自己的工具接入标准,只需要满足MCP协议,就可以立马使用满足MCP协议的工具。可以把MCP想象成AI应用的USB接口,USB接口统一了电脑与外部显示器、鼠标等工具/系统的链接方式,就像MCP统一了AI应用与外部系统的链接方式一样。

MCP协议是帮助AI智能应用(准确说是LLM应用)快速连接外部系统(一般是工具、函数、外部数据源等)的开源标准协议,因此涉及的相关方有:LLM、智能应用(MCP Host)、外部系统(MCP Server)。

注意:MCP协议是帮助AI智能应用连接外部系统的开源标准协议,通俗来说,它只作用与发现工具和调用工具这个范围,并没有规定智能应用如何与LLM 进行交互。MCP与function call,是不相交的两个作用域。因此,MCP与function call并不是替代关系,很多人可能都有理解偏差。

MCP Host

MCP Host是指支持MCP协议,可以接入MCP Server的AI应用程序,就像支持USB的电脑,比如Cursor、Claude Code、Trae、Cherry Studio等。

MCP Server

MCP Server是指支持MCP协议,提供工具、资源等的外部系统的程序,就像支持USB的键盘、鼠标。MCP Server很多,可以在MCP Server市场找到,比如魔搭社区(https://www.modelscope.cn/mcp)、MCP官方集合(https://github.com/modelcontextprotocol/servers)。

MCP Server本质是一段程序,它可以是在本地机器运行的本地程序,也可以是远程运行的程序。与MCP Host在同一个计算机上执行的MCP Server程序,通常被称为“本地服务器”Local MCP Server,通过STUDIO传输。MCP Host与远程机器运行的MCP Server程序,通常被称为“远程服务器”Remote MCP Server,通过SSE或者可流失传输的HTTP协议进行传输。

以魔搭社区的MCP Server “Fetch网页内容抓取工具”为例,提供Studio和Remote两种配置方式:

链接到本地MCP服务器Local MCP Server

本地MCP服务器,是运行在MCP Host同一台主机的程序,通常是用python或者node.js写的脚本程序。

安装Fetch网页内容抓取工具的本地MCP服务器,终端执行命令:

uvx mcp-server-fetch

该命令会安装Python语言编写的脚本程序及其相应的依赖,并执行脚本程序。简单来说就是会先下载程序到电脑,然后执行程序。由于MCP Server是跟MCP Host进行传输/通信,因此运行这个命令并不会在命令行输出内容。

然后,我们可以使用MCP Host配置这个Local MCP Server,以Cherry Studio为例,可以通过可视化,也可以通过json配置Cherry Studio的MCP Servers。

{
  "mcpServers": {
    "fetch": {
      "args": [
        "mcp-server-fetch"
      ],
      "command": "uvx"
    }
  }
}

然后,我在AI对话界面,设置该MCP服务器为自动识别,Cherry Studio这个AI应用程序的智能助手,就具备了使用Fetch这个工具的能力。比如我询问“请将这个网页的内容翻译成中文:https://docs.python.org/3/faq/general.html#how-stable-is-python”,智能助手会查询工具、调用执行工具,然后根据执行结果返回我需要的内容。换句话说,智能助手具备了使用工具完成任务的能力。

# 强子
请将这个网页的内容翻译成日文。
https://docs.python.org/3/faq/general.html#how-stable-is-python


# 助手
我需要先获取这个网页的内容,然后将其翻译成日文。让我使用可用的工具来完成这个任务。

首先,我需要查看有哪些可用的工具:

很好!有一个 `fetchFetch` 工具可以获取网页内容。让我先检查一下这个工具的详细信息:

现在我需要获取网页内容。让我使用 `exec` 来获取网页内容并将其翻译成日文:

我将手动将内容翻译成日文。以下是Python FAQ页面的日文翻译:

我来帮您将整个Python FAQ页面翻译成日文。首先让我获取完整的页面内容:

让我先查看可用的工具,看看有哪些工具可以用来获取网页内容:

很好!有一个fetch工具。让我先检查一下这个工具的详细参数:

现在让我获取网页内容,然后查看是否有翻译工具:

看起来只有fetch工具可用,没有直接的翻译工具。让我先获取网页内容,然后我可以手动翻译:

内容被截断了。让我获取更多内容:

由于内容很长,让我分部分获取并翻译。我将手动将内容翻译成日文。以下是Python FAQ页面的日文翻译:

# Python一般FAQ

## 目次

* [Python一般FAQ](#python一般faq)

  + [一般情報](#一般情報)

    - [Pythonとは何ですか?](#pythonとは何ですか)
    - [Pythonソフトウェア財団とは何ですか?](#pythonソフトウェア財団とは何ですか)
    - [Pythonの使用に著作権制限はありますか?](#pythonの使用に著作権制限はありますか)
    - [そもそもなぜPythonは作成されたのですか?](#そもそもなぜpythonは作成されたのですか)
    - [Pythonは何に適していますか?](#pythonは何に適していますか)
    - [Pythonのバージョン番号体系はどのように機能しますか?](#pythonのバージョン番号体系はどのように機能しますか)

...

链接到远程MCP服务器Remote MCP Server

远程MCP服务器,是与MCP Host运行在非同一台机器上的远程服务(程序)。远程MCP服务器的传输方式有SSE和streamable_http两种。同样以Fetch网页内容抓取工具为例,魔搭社区提供Remote MCP Server,我们只需要申请一个API Token即可使用。

在Cherry Studio配置远程MCP服务器,配置智能助手自动发现该MCP服务器。

{ "mcpServers": { "fetch": { "type": "streamable_http", "url": "https://mcp.api-inference.modelscope.net/8f0e05475da447/mcp", "headers": { "Authorization": "Bearer xxxxx" } } } }

与智能助手对话,可以达到与本地MCP服务器相同的效果。

MCP Host与Coze的工具配置差异

Coze的工具配置

如果使用过Coze搭建过智能体Agent,可以知道Coze是通过配置插件、工作流、知识库进行工具调用,本质来说都是调用HTTP API。也就是说,Coze的工具注册,是挂载预先定义好的API。LLM如何知道什么时候调用工具,是通过工具的描述和参数信息进行推理。当然,还有系统提示词System prompt。

Coze的智能体Agent是否高效使用工具,取决于工具的API描述是否详细,以及Agent的系统提示词是否能

然后,用户在与Coze智能体进行对话时,Coze会执行function call的业务逻辑,也就是每次都会把配置的这些工具以tools的方式,塞在调用LLM API的json里。比如,配置了两个插件DayWeather(获取国内指定日期的天气,不支持获取国外天气情况)、SearchStockData(根据关键词或者股票代码搜索股票信息,包括财务状况以及市场信息)。如果询问“明天什么天气”,则调用LLM API时,会将配置的两个工具带入到API参数中。注意观察“tools”。

{"messages":[{"content":"今天是 2026年03月04日 22时58分20秒 星期三 。# 角色\n你是一个多能力智能体\n\n\n\n","role":"system"},

{"content":"明天成都什么天气?","role":"user"}],"model":"DeepSeek-V3·工具调用","model_meta":{"api_mode":0,"max_tokens":4096,"stream":true,"temperature":0.8},


"tools":[{"function":{"description":"获取国内指定日期的天气,不支持获取国外天气情况。","name":"mojitianqi-DayWeather","parameters":{"description":"","properties":{"city":{"description":"市名,包括直辖市,比如:北京市、天津市、上海市、重庆市","properties":{},"type":"string"},"end_time":{"description":"待查询结束日期","properties":{},"type":"string"},"province":{"description":"省份名,不要包括直辖市(比如:北京、北京市、北京省、天津市、上海市、重庆市)","properties":{},"type":"string"},"start_time":{"description":"待查询开始日期","properties":{},"type":"string"},"towns":{"description":"区/县/镇","properties":{},"type":"string"},"villages":{"description":"乡/村","properties":{},"type":"string"}},"type":"object"}},"type":"function"},{"function":{"description":"根据关键词或者股票代码搜索股票信息,包括财务状况以及市场信息。例子:贵州茅台、600519","name":"xinlangcaijing-SearchStockData","parameters":{"description":"","properties":{"date":{"description":"指定日期,默认今天。格式为YYYY-MM-DD","properties":{},"type":"string"},"keyword":{"description":"股票的关键词或者股票代码,例如:茅台、腾讯、600519","properties":{},"type":"string"}},"required":["keyword"],"type":"object"}},"type":"function"}]}

然后,支持function call的LLM API,在LLM进行推理后,会返回调用工具的指令,如下:

{"choices":[{"index":0,"message":{"content":"","role":"assistant","tool_calls":[{"function":{"arguments":"{\"start_time\":\"2026-03-05\",\"end_time\":\"2026-03-05\",\"city\":\"成都市\"}","name":"mojitianqi-DayWeather"}}]},"stop_reason":"stop"}],"model":"DeepSeek-V3·工具调用","usage":{"completion_tokens":56,"completion_tokens_details":{"reasoning_tokens":0},"prompt_tokens":1421,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":1477}}

Coze的Agent调度引擎,会去调工具API,并将结果作为上下文,再调用LLM API,最终会输出结果:

根据天气预报,明天(2026年3月5日)成都的天气情况如下: 🌤️ 天气:多云 🌡️ 温度:10°C - 22°C 💨 白天风力:西北风1级 🌙 夜间风力:西南风2级 💧 湿度:62% 总体来说明天成都天气不错,多云天气,温度适宜,早晚温差较大,建议穿着适当保暖的衣物。

MCP Host与MCP Server的工具配置

Coze是通过在编排Agent时,显性挂载HTTP API形式的插件、工作流、知识库作为工具。那MCP Host是如何发现MCP Server的工具(或其他资源)的呢?

实际上,MCP Host在配置MCP Server时,会进行一个初始化,MCP Server会将自己的服务(工具、资源、提示词)暴露给MCP Host。

为了深入了解这个过程是如何实现的,我决定写一个MCP Server,以Claude官方案例为例:

from typing import Any

import httpx
from mcp.server.fastmcp import FastMCP

# 创建FastMCP server对象
mcp = FastMCP("weather", log_level="ERROR")

# 常量
# 天气查询API 的baseurl
NWS_API_BASE = "https://api.weather.gov"
# 用户代理UA
USER_AGENT = "weather-app/1.0"


# 辅助函数:
# 用于向NWS API 发送请求的异步函数
async def make_nws_request(url: str) -> dict[str, Any] | None:
    """Make a request to the NWS API with proper error handling."""
    headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

# 辅助函数:
# 用于格式化天气预警信息的函数
def format_alert(feature: dict) -> str:
    """Format an alert feature into a readable string."""
    props = feature["properties"]
    return f"""
Event: {props.get("event", "Unknown")}
Area: {props.get("areaDesc", "Unknown")}
Severity: {props.get("severity", "Unknown")}
Description: {props.get("description", "No description available")}
Instructions: {props.get("instruction", "No specific instructions provided")}
"""


# 开发两个MCP方法:
# 1. 获取天气查询信息

@mcp.tool()
async def get_alerts(state: str) -> str:
    # 文档字符串用于说明该函数的作用和参数
    """Get weather alerts for a US state.

    Args:
        state: Two-letter US state code (e.g. CA, NY)
    """
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)

    if not data or "features" not in data:
        return "Unable to fetch alerts or no alerts found."

    if not data["features"]:
        return "No active alerts for this state."

    alerts = [format_alert(feature) for feature in data["features"]]
    return "\n---\n".join(alerts)

# 2. 获取天气预警信息
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location
    """
    # First get the forecast grid endpoint
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    # Get the forecast URL from the points response
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "Unable to fetch detailed forecast."

    # Format the periods into a readable forecast
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:  # Only show next 5 periods
        forecast = f"""
{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}
"""
        forecasts.append(forecast)

    return "\n---\n".join(forecasts)


# 主函数:
# 用于初始化和运行MCP server
def main():
    # Initialize and run the server
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()

然后,专门使用一个脚本文件,用来记录MCP Host在添加MCP Server时,双方通信的详细日志。此处感谢B站大佬的”马克的技术工作坊“的脚本文件。

#!/usr/bin/env python3

import sys
import subprocess
import threading
import argparse
import os

# --- Configuration ---
LOG_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mcp_io.log")
# --- End Configuration ---

# --- Argument Parsing ---
parser = argparse.ArgumentParser(
    description="Wrap a command, passing STDIN/STDOUT verbatim while logging them.",
    usage="%(prog)s <command> [args...]"
)
# Capture the command and all subsequent arguments
parser.add_argument('command', nargs=argparse.REMAINDER,
                    help='The command and its arguments to execute.')

open(LOG_FILE, 'w', encoding='utf-8')

if len(sys.argv) == 1:
    parser.print_help(sys.stderr)
    sys.exit(1)

args = parser.parse_args()

if not args.command:
    print("Error: No command provided.", file=sys.stderr)
    parser.print_help(sys.stderr)
    sys.exit(1)

target_command = args.command
# --- End Argument Parsing ---

# --- I/O Forwarding Functions ---
# These will run in separate threads

def forward_and_log_stdin(proxy_stdin, target_stdin, log_file):
    """Reads from proxy's stdin, logs it, writes to target's stdin."""
    try:
        while True:
            # Read line by line from the script's actual stdin
            line_bytes = proxy_stdin.readline()
            if not line_bytes:  # EOF reached
                break

            # Decode for logging (assuming UTF-8, adjust if needed)
            try:
                 line_str = line_bytes.decode('utf-8')
            except UnicodeDecodeError:
                 line_str = f"[Non-UTF8 data, {len(line_bytes)} bytes]\n" # Log representation

            # Log with prefix
            log_file.write(f"输入: {line_str}")
            log_file.flush() # Ensure log is written promptly

            # Write the original bytes to the target process's stdin
            target_stdin.write(line_bytes)
            target_stdin.flush() # Ensure target receives it promptly

    except Exception as e:
        # Log errors happening during forwarding
        try:
            log_file.write(f"!!! STDIN Forwarding Error: {e}\n")
            log_file.flush()
        except: pass # Avoid errors trying to log errors if log file is broken

    finally:
        # Important: Close the target's stdin when proxy's stdin closes
        # This signals EOF to the target process (like test.sh's read loop)
        try:
            target_stdin.close()
            log_file.write("--- STDIN stream closed to target ---\n")
            log_file.flush()
        except Exception as e:
             try:
                log_file.write(f"!!! Error closing target STDIN: {e}\n")
                log_file.flush()
             except: pass


def forward_and_log_stdout(target_stdout, proxy_stdout, log_file):
    """Reads from target's stdout, logs it, writes to proxy's stdout."""
    try:
        while True:
            # Read line by line from the target process's stdout
            line_bytes = target_stdout.readline()
            if not line_bytes: # EOF reached (process exited or closed stdout)
                break

            # Decode for logging
            try:
                 line_str = line_bytes.decode('utf-8')
            except UnicodeDecodeError:
                 line_str = f"[Non-UTF8 data, {len(line_bytes)} bytes]\n"

            # Log with prefix
            log_file.write(f"输出: {line_str}")
            log_file.flush()

            # Write the original bytes to the script's actual stdout
            proxy_stdout.write(line_bytes)
            proxy_stdout.flush() # Ensure output is seen promptly

    except Exception as e:
        try:
            log_file.write(f"!!! STDOUT Forwarding Error: {e}\n")
            log_file.flush()
        except: pass
    finally:
        try:
            log_file.flush()
        except: pass
        # Don't close proxy_stdout (sys.stdout) here

# --- Main Execution ---
process = None
log_f = None
exit_code = 1 # Default exit code in case of early failure

try:
    # Open log file in append mode ('a') for the threads
    log_f = open(LOG_FILE, 'a', encoding='utf-8')

    # Start the target process
    # We use pipes for stdin/stdout
    # We work with bytes (bufsize=0 for unbuffered binary, readline() still works)
    # stderr=subprocess.PIPE could be added to capture stderr too if needed.
    process = subprocess.Popen(
        target_command,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE, # Capture stderr too, good practice
        bufsize=0 # Use 0 for unbuffered binary I/O
    )

    # Pass binary streams to threads
    stdin_thread = threading.Thread(
        target=forward_and_log_stdin,
        args=(sys.stdin.buffer, process.stdin, log_f),
        daemon=True # Allows main thread to exit even if this is stuck (e.g., waiting on stdin) - reconsider if explicit join is needed
    )

    stdout_thread = threading.Thread(
        target=forward_and_log_stdout,
        args=(process.stdout, sys.stdout.buffer, log_f),
        daemon=True
    )

    # Optional: Handle stderr similarly (log and pass through)
    stderr_thread = threading.Thread(
        target=forward_and_log_stdout, # Can reuse the function
        args=(process.stderr, sys.stderr.buffer, log_f), # Pass stderr streams
        # Add a different prefix in the function if needed, or modify function
        # For now, it will log with "STDOUT:" prefix - might want to change function
        # Let's modify the function slightly for this
        daemon=True
    )
    # A slightly modified version for stderr logging
    def forward_and_log_stderr(target_stderr, proxy_stderr, log_file):
        """Reads from target's stderr, logs it, writes to proxy's stderr."""
        try:
            while True:
                line_bytes = target_stderr.readline()
                if not line_bytes: break
                try: line_str = line_bytes.decode('utf-8')
                except UnicodeDecodeError: line_str = f"[Non-UTF8 data, {len(line_bytes)} bytes]\n"
                log_file.write(f"STDERR: {line_str}") # Use STDERR prefix
                log_file.flush()
                proxy_stderr.write(line_bytes)
                proxy_stderr.flush()
        except Exception as e:
            try:
                log_file.write(f"!!! STDERR Forwarding Error: {e}\n")
                log_file.flush()
            except: pass
        finally:
            try:
                log_file.flush()
            except: pass

    stderr_thread = threading.Thread(
        target=forward_and_log_stderr,
        args=(process.stderr, sys.stderr.buffer, log_f),
        daemon=True
    )


    # Start the forwarding threads
    stdin_thread.start()
    stdout_thread.start()
    stderr_thread.start() # Start stderr thread too

    # Wait for the target process to complete
    process.wait()
    exit_code = process.returncode

    # Wait briefly for I/O threads to finish flushing last messages
    # Since they are daemons, they might exit abruptly with the main thread.
    # Joining them ensures cleaner shutdown and logging.
    # We need to make sure the pipes are closed so the reads terminate.
    # process.wait() ensures target process is dead, pipes should close naturally.
    stdin_thread.join(timeout=1.0) # Add timeout in case thread hangs
    stdout_thread.join(timeout=1.0)
    stderr_thread.join(timeout=1.0)


except Exception as e:
    print(f"MCP Logger Error: {e}", file=sys.stderr)
    # Try to log the error too
    if log_f and not log_f.closed:
        try:
            log_f.write(f"!!! MCP Logger Main Error: {e}\n")
            log_f.flush()
        except: pass # Ignore errors during final logging attempt
    exit_code = 1 # Indicate logger failure

finally:
    # Ensure the process is terminated if it's still running (e.g., if logger crashed)
    if process and process.poll() is None:
        try:
            process.terminate()
            process.wait(timeout=1.0) # Give it a moment to terminate
        except: pass # Ignore errors during cleanup
        if process.poll() is None: # Still running?
             try: process.kill() # Force kill
             except: pass # Ignore kill errors

    # Final log message
    if log_f and not log_f.closed:
        try:
            log_f.close()
        except: pass # Ignore errors during final logging attempt

    # Exit with the target process's exit code
    sys.exit(exit_code)

然后,修改MCP Server的启动配置json,将MCP LOG的脚本作为启动程序

{
  "mcpServers": {
    "weather": {
      "command": "python3",
      "args": [
        "/Users/zhongziqiang/code/weather/mcp_logger.py",
        "uv",
        "--directory",
        "/Users/zhongziqiang/code/weather",
        "run",
        "weather.py"
      ]
    }
  }
}

使用Cherry Studio作为MCP Host,在配置weather MCP Server时,使用这个配置文件,即可成功配置自己写的MCP Server,同时会得到r在初始化时的通信日志。其中,”输入“代表Cherry Studio(MCP Host)到weather(MCP Server)的数据流向,”输出“则相反。

输入: {"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"Cherry Studio","version":"1.7.19"}},"jsonrpc":"2.0","id":0}
输出: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-06-18","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather","version":"1.26.0"}}}
输入: {"method":"notifications/initialized","jsonrpc":"2.0"}
输入: {"method":"tools/list","jsonrpc":"2.0","id":1}
输出: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\n    Args:\n        state: Two-letter US state code (e.g. CA, NY)\n    ","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_alertsOutput","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\n    Args:\n        latitude: Latitude of the location\n        longitude: Longitude of the location\n    ","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_forecastOutput","type":"object"}}]}}
输入: {"method":"ping","jsonrpc":"2.0","id":2}
输出: {"jsonrpc":"2.0","id":2,"result":{}}
输入: {"method":"ping","jsonrpc":"2.0","id":3}
输入: {"method":"ping","jsonrpc":"2.0","id":4}
输入: {"method":"ping","jsonrpc":"2.0","id":5}
输入: {"method":"ping","jsonrpc":"2.0","id":6}
输出: {"jsonrpc":"2.0","id":3,"result":{}}
输入: {"method":"tools/list","jsonrpc":"2.0","id":7}
输出: {"jsonrpc":"2.0","id":4,"result":{}}
输入: {"method":"prompts/list","jsonrpc":"2.0","id":8}
输出: {"jsonrpc":"2.0","id":5,"result":{}}
输入: {"method":"resources/list","jsonrpc":"2.0","id":9}
输出: {"jsonrpc":"2.0","id":6,"result":{}}
输出: {"jsonrpc":"2.0","id":7,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\n    Args:\n        state: Two-letter US state code (e.g. CA, NY)\n    ","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_alertsOutput","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\n    Args:\n        latitude: Latitude of the location\n        longitude: Longitude of the location\n    ","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_forecastOutput","type":"object"}}]}}
输出: {"jsonrpc":"2.0","id":8,"result":{"prompts":[]}}
输出: {"jsonrpc":"2.0","id":9,"result":{"resources":[]}}

日志记录了MCP Host与MCP Server之间的一次完整注册和能力发现过程,对日志分析如下:

  1. 首先,MCP Host给MCP Server作初始化握手,Host向Server发起通信,协商协议的版本protocolVersion、能力信息capabilities和自身信息clientInfo等。
{
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": { "name": "Cherry Studio", "version": "1.7.19" }
  },
  "jsonrpc": "2.0",
  "id": 0
}

Server确认协议版本protocolVersion,返回自身能力capabilities与标识serverInfo。

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "experimental": {},
      "prompts": { "listChanged": false },
      "resources": { "subscribe": false, "listChanged": false },
      "tools": { "listChanged": false }
    },
    "serverInfo": { "name": "weather", "version": "1.26.0" }
  }
}
  1. 然后,Host通知Server初始化完成了,后续可发送其他请求。此消息无需响应,因此可以看到只有输入,没有输出
{ "method": "notifications/initialized", "jsonrpc": "2.0" }
  1. Host获取Server的工具列表
{
  "method": "tools/list",
  "jsonrpc": "2.0",
  "id": 1
}

Server响应,将自己的工具列表返回给Host。其中name是工具名称,是编写weather.py定义的工具名。description是工具功能的说明和参数解释,这个内容取自编写weather.py中工具函数的文档注释。inputSchema是工具输入参数的JSON Schema,说明了输入参数的参数名、参数类型、是否必填等。outputSchema是工具输出结果的JSON Schema,说明了输出参数的名称、类型等信息。

需要注意,相对于Coze挂载的工具列表信息,MCP Server的工具和详细描述更为准确,采用了JSON Schema,让LLM更为准确的了解到工具的具体作用,以及需要调用工具时,使用更为合适的参数。而Coze仅提供输入参数描述,输出完全依赖API返回的自然语言文本或非结构化数据。LLM需要自行理解文本内容,容易产生歧义或错误解析。并且,Coze的工具可能没有对参数进行可选的描述,这会导致LLM可能在不必要的情况下省略参数,或无法正确构造调用。

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_alerts",
        "description": "Get weather alerts for a US state.\n\n    Args:\n        state: Two-letter US state code (e.g. CA, NY)\n    ",
        "inputSchema": {
          "properties": { "state": { "title": "State", "type": "string" } },
          "required": ["state"],
          "title": "get_alertsArguments",
          "type": "object"
        },
        "outputSchema": {
          "properties": { "result": { "title": "Result", "type": "string" } },
          "required": ["result"],
          "title": "get_alertsOutput",
          "type": "object"
        }
      },
      {
        "name": "get_forecast",
        "description": "Get weather forecast for a location.\n\n    Args:\n        latitude: Latitude of the location\n        longitude: Longitude of the location\n    ",
        "inputSchema": {
          "properties": {
            "latitude": { "title": "Latitude", "type": "number" },
            "longitude": { "title": "Longitude", "type": "number" }
          },
          "required": ["latitude", "longitude"],
          "title": "get_forecastArguments",
          "type": "object"
        },
        "outputSchema": {
          "properties": { "result": { "title": "Result", "type": "string" } },
          "required": ["result"],
          "title": "get_forecastOutput",
          "type": "object"
        }
      }
    ]
  }
}

下面是Coze的工具描述,仅做对比

{"description":"获取国内指定日期的天气,不支持获取国外天气情况。","name":"mojitianqi-DayWeather","parameters":{"description":"","properties":{"city":{"description":"市名,包括直辖市,比如:北京市、天津市、上海市、重庆市","properties":{},"type":"string"},"end_time":{"description":"待查询结束日期","properties":{},"type":"string"},"province":{"description":"省份名,不要包括直辖市(比如:北京、北京市、北京省、天津市、上海市、重庆市)","properties":{},"type":"string"},"start_time":{"description":"待查询开始日期","properties":{},"type":"string"},"towns":{"description":"区/县/镇","properties":{},"type":"string"},"villages":{"description":"乡/村","properties":{},"type":"string"}},"type":"object"}},"type":"function"}
  1. 接下来的日志为多次心跳检测(Ping),Host检查链接是否存活,而Server返回空对象标识正常
{ "method": "ping", "jsonrpc": "2.0", "id": 2 }
{ "jsonrpc": "2.0", "id": 2, "result": {} }
  1. 之后,Host再次获取了工具列表,可能是用于刷新工具和验证
{ "method": "tools/list", "jsonrpc": "2.0", "id": 7 }

Server返回了相同的结果

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": { "tools": [ ... ] }  // 与第一次返回完全相同
}
  1. MCP Server不仅包含工具,还有资源和提示词。Host请求获取提示词列表:
{ "method": "prompts/list", "jsonrpc": "2.0", "id": 8 }

Server回复提示词列表,返回空列表标识不支持prompts

{ "jsonrpc": "2.0", "id": 8, "result": { "prompts": [] } }
  1. Host获取资源列表
{ "method": "resources/list", "jsonrpc": "2.0", "id": 9 }

Server回复资源列表,返回了空列表,表示不支持resources

{ "jsonrpc": "2.0", "id": 9, "result": { "resources": [] } }

总结

  1. 配置方式方面:Coze 在编排 Agent 时显式挂载 HTTP API 插件,每次调用 LLM 都会在 tools 参数中附带所有工具的描述信息;MCP Host 则通过协议与 MCP Server 进行初始化握手,动态发现 Server 提供的工具、资源和提示词,理论上每次调用LLM也会在参数中附带所有MCP Server工具信息,此处暂不探讨,因为MCP并不规定MCP Host与LLM的通信方式和详细逻辑。

  2. 工具描述的精确度方面:Coze 的工具描述仅提供参数的文字说明,输出格式不固定;MCP 使用 JSON Schema 严格定义输入参数的类型、必填项以及输出结果的格式,有助于 LLM 更准确地理解工具的用法和预期返回。

  3. 工具集的管理方面:Coze 的工具列表在 Agent 定义时确定,相对静态;MCP Server 可以动态更新其能力,Host 通过 listChanged 机制获知变化,使工具集更具灵活性。

MCP Host与Coze的工具调用差异

Coze的工具调用

Coze的智能体配置工具后,完整的调用逻辑如下:

假如我输入“我在成都,今天天气如何”,对话上下文仅包含上次询问”你是谁“的对话。智能体会调用墨迹天气DayWeather工具,可能会输出”明天成都的天气为多云,东北风 2 级,气温 10℃到 18℃,湿度 76%。“

用户询问天气的调度流程为:用户输入 -> 调度器调用LLM -> LLM输出调用墨迹天气DayWeather工具的指令给调度器 -> 调度器调用工具墨迹天气DayWeather -> 墨迹天气DayWeather工具执行并将结果返回给调度器 -> 调度器将工具执行结果+用户输入+上下文 调用LLM -LLM输出回复内容给调度器 -> 调度器将回复内容输出给用户 -> 智能体回复。

第一次调用LLM的输入如下:由此可以了解到,LLM调用时包含以下信息:

  1. message:包含系统提示词、用户输入,对话历史(上下文)。
  2. model:LLM大模型相关信息,包括模型名称,模型参数等。
  3. tools:工具,所有可调用工具的信息。
{"messages":[{"content":"今天是 2026年02月28日 15时23分47秒 星期六 。# 角色\n你是一名强子开发的智能客服。\n\n","role":"system"},{"content":"你是谁?","role":"user"},{"content":"我是强子开发的智能客服,能够为您提供信息咨询、问题解答等服务。","role":"assistant"},{"content":"我在成都,明天天气如何?","role":"user"}],"model":"豆包·1.6·极致速度·250828","model_meta":{"api_mode":0,"frequency_penalty":0,"max_tokens":4096,"parameters":{"max_completion_tokens":"0","thinking_type":"disabled"},"stream":true,"temperature":0.8,"top_p":1},"tools":[{"function":{"description":"获取国内指定日期的天气,不支持获取国外天气情况。","name":"mojitianqi-DayWeather","parameters":{"description":"","properties":{"city":{"description":"市名,包括直辖市,比如:北京市、天津市、上海市、重庆市","properties":{},"type":"string"},"end_time":{"description":"待查询结束日期","properties":{},"type":"string"},"province":{"description":"省份名,不要包括直辖市(比如:北京、北京市、北京省、天津市、上海市、重庆市)","properties":{},"type":"string"},"start_time":{"description":"待查询开始日期","properties":{},"type":"string"},"towns":{"description":"区/县/镇","properties":{},"type":"string"},"villages":{"description":"乡/村","properties":{},"type":"string"}},"type":"object"}},"type":"function"},{"function":{"description":"驾驶路线规划","name":"gaodedetu-plan_driving","parameters":{"description":"","properties":{"destination":{"description":"目标地点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"},"disable_paths":{"description":"是否禁止返回路径详情, default value is false","properties":{},"type":"boolean"},"origin":{"description":"起点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"}},"required":["destination","origin"],"type":"object"}},"type":"function"},{"function":{"description":"骑行路线规划","name":"gaodedetu-plan_bicycle","parameters":{"description":"","properties":{"destination":{"description":"目标地点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"},"disable_paths":{"description":"是否禁止展示路径详情, default value is false","properties":{},"type":"boolean"},"origin":{"description":"起点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"}},"required":["destination","origin"],"type":"object"}},"type":"function"}]}

LLM调用输出如下,包含以下信息:

  1. choices:包含回复信息。注意,回复信息中stop_reason字段值为tool_calls,用于告诉调度器去调用工具。并且在function参数值内明确了要调用的工具名称和调用参数。
  2. model:包含LLM信息。
  3. usage:包含调用LLM的消费token用量信息。
{"choices":[{"index":0,"message":{"content":"","role":"assistant","tool_calls":[{"function":{"arguments":"{\"city\":\"成都市\",\"start_time\":\"2026年03月01日\",\"end_time\":\"2026年03月01日\"}","name":"mojitianqi-DayWeather"}}]},"stop_reason":"tool_calls"}],"model":"豆包·1.6·极致速度·250828","usage":{"completion_tokens":48,"completion_tokens_details":{"reasoning_tokens":0},"prompt_tokens":906,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":954}}

然后调度器调用墨迹天气DayWeather工具,返回天气的查询结果。

{"output":"{\"data\":[{\"wind_dir_day\":\"东北风\",\"wind_level_day\":\"2\",\"temp_high\":18,\"condition\":\"多云\",\"wind_dir_night\":\"东北风\",\"predict_date\":\"2026-03-01\",\"weather_day\":\"多云\",\"wind_level_night\":\"2\",\"temp_low\":10,\"humidity\":76}]}"}

第二次调用LLM的输入如下,相比于上次调用,在message里面增加了一个工具调用和工具调用结果。

{"messages":[{"content":"今天是 2026年02月28日 15时23分47秒 星期六 。# 角色\n你是一名强子开发的智能客服。\n\n","role":"system"},{"content":"你是谁?","role":"user"},{"content":"我是强子开发的智能客服,能够为您提供信息咨询、问题解答等服务。","role":"assistant"},{"content":"我在成都,明天天气如何?","role":"user"},

{"content":"","role":"assistant","tool_calls":[{"function":{"arguments":"{\"city\":\"成都市\",\"start_time\":\"2026年03月01日\",\"end_time\":\"2026年03月01日\"}","name":"mojitianqi-DayWeather"}}]},

{"content":"{\"data\":[{\"wind_dir_day\":\"东北风\",\"wind_level_day\":\"2\",\"temp_high\":18,\"condition\":\"多云\",\"wind_dir_night\":\"东北风\",\"predict_date\":\"2026-03-01\",\"weather_day\":\"多云\",\"wind_level_night\":\"2\",\"temp_low\":10,\"humidity\":76}]}","name":"mojitianqi-DayWeather","role":"tool"}],

"model":"豆包·1.6·极致速度·250828","model_meta":{"api_mode":0,"frequency_penalty":0,"max_tokens":4096,"parameters":{"max_completion_tokens":"0","thinking_type":"disabled"},"stream":true,"temperature":0.8,"top_p":1},"tools":[{"function":{"description":"获取国内指定日期的天气,不支持获取国外天气情况。","name":"mojitianqi-DayWeather","parameters":{"description":"","properties":{"city":{"description":"市名,包括直辖市,比如:北京市、天津市、上海市、重庆市","properties":{},"type":"string"},"end_time":{"description":"待查询结束日期","properties":{},"type":"string"},"province":{"description":"省份名,不要包括直辖市(比如:北京、北京市、北京省、天津市、上海市、重庆市)","properties":{},"type":"string"},"start_time":{"description":"待查询开始日期","properties":{},"type":"string"},"towns":{"description":"区/县/镇","properties":{},"type":"string"},"villages":{"description":"乡/村","properties":{},"type":"string"}},"type":"object"}},"type":"function"},{"function":{"description":"驾驶路线规划","name":"gaodedetu-plan_driving","parameters":{"description":"","properties":{"destination":{"description":"目标地点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"},"disable_paths":{"description":"是否禁止返回路径详情, default value is false","properties":{},"type":"boolean"},"origin":{"description":"起点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"}},"required":["destination","origin"],"type":"object"}},"type":"function"},{"function":{"description":"骑行路线规划","name":"gaodedetu-plan_bicycle","parameters":{"description":"","properties":{"destination":{"description":"目标地点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"},"disable_paths":{"description":"是否禁止展示路径详情, default value is false","properties":{},"type":"boolean"},"origin":{"description":"起点。可以是关键词;也可以是经纬度,经纬度之间用英文逗号分隔","properties":{},"type":"string"}},"required":["destination","origin"],"type":"object"}},"type":"function"}]}

MCP Host对MCP Server工具调用

在配置weather MCP Server的Cherry Studio MCP Host对智能体进行提问,可以用户与智能体的交互过程如下:


#用户
华盛顿今天是什么天气?

#DeepSeek
我来帮您查询华盛顿今天的天气。首先让我查看可用的工具。

{
  "params": {
    "limit": 50,
    "offset": 0
  },
  "response": {
    "content": [
      {
        "type": "text",
        "text": "Total: 2 tools\nOffset: 0\nLimit: 50\nReturned: 2\n\n- weatherGetAlerts (7oj3HAiq4XNp-yG1dNoEE__get_alerts): Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY)\n- weatherGetForecast (7oj3HAiq4XNp-yG1dNoEE__get_forecast): Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location"
      }
    ]
  }
}

我看到有天气预报工具。让我先检查一下这个工具的详细参数:

{
  "params": {
    "name": "weatherGetForecast"
  },
  "response": {
    "content": [
      {
        "type": "text",
        "text": "/**\n * Get weather forecast for a location.\n\n    Args:\n        latitude: Latitude of the location\n        longitude: Longitude of the location\n *\n * @param {Object} params - Parameters\n * @param {number} params.latitude - (required)\n * @param {number} params.longitude - (required)\n */\nfunction weatherGetForecast(params) {}"
      }
    ]
  }
}

我需要华盛顿的经纬度坐标。华盛顿特区(Washington D.C.)的坐标大约是纬度38.9072,经度-77.0369。让我使用这个工具查询天气:

{
  "params": {
    "name": "weatherGetForecast",
    "params": {
      "latitude": 38.9072,
      "longitude": -77.0369
    }
  },
  "response": {
    "content": [
      {
        "type": "text",
        "text": "\nToday:\nTemperature: 63°F\nWind: 2 to 7 mph E\nForecast: Areas of fog and isolated rain showers. Mostly cloudy, with a high near 63. East wind 2 to 7 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 46°F\nWind: 2 to 7 mph NE\nForecast: Scattered rain showers before 7pm, then isolated showers and thunderstorms between 7pm and 8pm, then patchy fog and scattered showers and thunderstorms between 8pm and 9pm, then areas of fog and a chance of showers and thunderstorms between 9pm and 10pm, then areas of fog and scattered rain showers. Mostly cloudy, with a low around 46. Northeast wind 2 to 7 mph. Chance of precipitation is 60%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday:\nTemperature: 56°F\nWind: 3 to 7 mph NE\nForecast: Patchy fog before 11am, then isolated rain showers. Mostly cloudy, with a high near 56. Northeast wind 3 to 7 mph. Chance of precipitation is 20%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday Night:\nTemperature: 46°F\nWind: 3 to 8 mph E\nForecast: A slight chance of rain showers before 10pm, then patchy fog and a slight chance of rain showers. Mostly cloudy, with a low around 46. East wind 3 to 8 mph. Chance of precipitation is 20%.\n\n---\n\nSaturday:\nTemperature: 75°F\nWind: 3 to 10 mph S\nForecast: A chance of rain showers and patchy fog. Partly sunny, with a high near 75. South wind 3 to 10 mph, with gusts as high as 20 mph. Chance of precipitation is 30%.\n"
      }
    ]
  }
}

根据天气预报,华盛顿特区(Washington D.C.)今天的天气情况如下:

今天(周四):

  • 温度: 63°F(约17°C)
  • 风力: 东风2-7英里/小时
  • 天气状况: 有雾和零星阵雨。大部分时间多云,最高温度约63°F。降水概率20%。

今晚:

  • 温度: 46°F(约8°C)
  • 风力: 东北风2-7英里/小时
  • 天气状况: 傍晚有分散阵雨,夜间有雾和雷阵雨可能性。大部分时间多云,最低温度约46°F。降水概率60%,预计降雨量小于0.1英寸。

总的来说,今天华盛顿天气比较凉爽,有雾和零星阵雨,建议出门带伞并注意保暖。


从Cherry Studio的用户界面输入输出来看,用户询问”华盛顿今天是什么天气?“,MCP Host多次与LLM和MCP Server进行通讯。其中,MCP Host与MCP Server进行的通信被日志详细记录下来,我们可以由此分析MCP Host对MCP Server工具调用的详细过程。日志如下:

输入: {"method":"ping","jsonrpc":"2.0","id":55}
输出: {"jsonrpc":"2.0","id":55,"result":{}}
输入: {"method":"tools/list","jsonrpc":"2.0","id":56}
输出: {"jsonrpc":"2.0","id":56,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\n    Args:\n        state: Two-letter US state code (e.g. CA, NY)\n    ","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_alertsOutput","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\n    Args:\n        latitude: Latitude of the location\n        longitude: Longitude of the location\n    ","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_forecastOutput","type":"object"}}]}}
输入: {"method":"ping","jsonrpc":"2.0","id":57}
输出: {"jsonrpc":"2.0","id":57,"result":{}}
输入: {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":38.9072,"longitude":-77.0369},"_meta":{"progressToken":58}},"jsonrpc":"2.0","id":58}
输出: {"jsonrpc":"2.0","id":58,"result":{"content":[{"type":"text","text":"\nToday:\nTemperature: 63°F\nWind: 2 to 7 mph E\nForecast: Areas of fog and isolated rain showers. Mostly cloudy, with a high near 63. East wind 2 to 7 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 46°F\nWind: 2 to 7 mph NE\nForecast: Scattered rain showers before 7pm, then isolated showers and thunderstorms between 7pm and 8pm, then patchy fog and scattered showers and thunderstorms between 8pm and 9pm, then areas of fog and a chance of showers and thunderstorms between 9pm and 10pm, then areas of fog and scattered rain showers. Mostly cloudy, with a low around 46. Northeast wind 2 to 7 mph. Chance of precipitation is 60%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday:\nTemperature: 56°F\nWind: 3 to 7 mph NE\nForecast: Patchy fog before 11am, then isolated rain showers. Mostly cloudy, with a high near 56. Northeast wind 3 to 7 mph. Chance of precipitation is 20%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday Night:\nTemperature: 46°F\nWind: 3 to 8 mph E\nForecast: A slight chance of rain showers before 10pm, then patchy fog and a slight chance of rain showers. Mostly cloudy, with a low around 46. East wind 3 to 8 mph. Chance of precipitation is 20%.\n\n---\n\nSaturday:\nTemperature: 75°F\nWind: 3 to 10 mph S\nForecast: A chance of rain showers and patchy fog. Partly sunny, with a high near 75. South wind 3 to 10 mph, with gusts as high as 20 mph. Chance of precipitation is 30%.\n"}],"structuredContent":{"result":"\nToday:\nTemperature: 63°F\nWind: 2 to 7 mph E\nForecast: Areas of fog and isolated rain showers. Mostly cloudy, with a high near 63. East wind 2 to 7 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 46°F\nWind: 2 to 7 mph NE\nForecast: Scattered rain showers before 7pm, then isolated showers and thunderstorms between 7pm and 8pm, then patchy fog and scattered showers and thunderstorms between 8pm and 9pm, then areas of fog and a chance of showers and thunderstorms between 9pm and 10pm, then areas of fog and scattered rain showers. Mostly cloudy, with a low around 46. Northeast wind 2 to 7 mph. Chance of precipitation is 60%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday:\nTemperature: 56°F\nWind: 3 to 7 mph NE\nForecast: Patchy fog before 11am, then isolated rain showers. Mostly cloudy, with a high near 56. Northeast wind 3 to 7 mph. Chance of precipitation is 20%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday Night:\nTemperature: 46°F\nWind: 3 to 8 mph E\nForecast: A slight chance of rain showers before 10pm, then patchy fog and a slight chance of rain showers. Mostly cloudy, with a low around 46. East wind 3 to 8 mph. Chance of precipitation is 20%.\n\n---\n\nSaturday:\nTemperature: 75°F\nWind: 3 to 10 mph S\nForecast: A chance of rain showers and patchy fog. Partly sunny, with a high near 75. South wind 3 to 10 mph, with gusts as high as 20 mph. Chance of precipitation is 30%.\n"},"isError":false}}

日志详细记录了MCP Host与MCP Server之间的一系列JSON-RPC交互,包括心跳维持、工具列表刷新和工具调用,最终成功获取天气数据。用。日志分析如下:

  1. 心跳Ping,Host Ping Server
{"method":"ping","jsonrpc":"2.0","id":55}

Server回复结果,表明链接存活

{"jsonrpc":"2.0","id":55,"result":{}}
  1. Host 获取工具列表
{"method":"tools/list","jsonrpc":"2.0","id":56}

Server回复工具列表,与初始化注册时相同

{
  "jsonrpc": "2.0",
  "id": 42,
  ... 
}
  1. 心跳Ping
  2. Host首次调用工具get_forecast,这是将LLM返回的调用工具的名称和调用参数进行解析,然后Host发送指令给Server去执行get_forecast工具。
{
  "method": "tools/call",
  "params": {
    "name": "get_forecast",
    "arguments": { "latitude": 38.9072, "longitude": -77.0369 },
    "_meta": { "progressToken": 58 }
  },
  "jsonrpc": "2.0",
  "id": 58
}

Server执行工具,即运行程序的函数。Server 成功返回华盛顿特区的天气预报数据。get_forecast函数是通过调用美国气象局API获得天气预测结果并进行字符串结构化处理。

{
  "jsonrpc": "2.0",
  "id": 58,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "\nToday:\nTemperature: 63°F\nWind: 2 to 7 mph E\nForecast: Areas of fog and isolated rain showers. Mostly cloudy, with a high near 63. East wind 2 to 7 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 46°F\nWind: 2 to 7 mph NE\nForecast: Scattered rain showers before 7pm, then isolated showers and thunderstorms between 7pm and 8pm, then patchy fog and scattered showers and thunderstorms between 8pm and 9pm, then areas of fog and a chance of showers and thunderstorms between 9pm and 10pm, then areas of fog and scattered rain showers. Mostly cloudy, with a low around 46. Northeast wind 2 to 7 mph. Chance of precipitation is 60%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday:\nTemperature: 56°F\nWind: 3 to 7 mph NE\nForecast: Patchy fog before 11am, then isolated rain showers. Mostly cloudy, with a high near 56. Northeast wind 3 to 7 mph. Chance of precipitation is 20%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday Night:\nTemperature: 46°F\nWind: 3 to 8 mph E\nForecast: A slight chance of rain showers before 10pm, then patchy fog and a slight chance of rain showers. Mostly cloudy, with a low around 46. East wind 3 to 8 mph. Chance of precipitation is 20%.\n\n---\n\nSaturday:\nTemperature: 75°F\nWind: 3 to 10 mph S\nForecast: A chance of rain showers and patchy fog. Partly sunny, with a high near 75. South wind 3 to 10 mph, with gusts as high as 20 mph. Chance of precipitation is 30%.\n"
      }
    ],
    "structuredContent": {
      "result": "\nToday:\nTemperature: 63°F\nWind: 2 to 7 mph E\nForecast: Areas of fog and isolated rain showers. Mostly cloudy, with a high near 63. East wind 2 to 7 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 46°F\nWind: 2 to 7 mph NE\nForecast: Scattered rain showers before 7pm, then isolated showers and thunderstorms between 7pm and 8pm, then patchy fog and scattered showers and thunderstorms between 8pm and 9pm, then areas of fog and a chance of showers and thunderstorms between 9pm and 10pm, then areas of fog and scattered rain showers. Mostly cloudy, with a low around 46. Northeast wind 2 to 7 mph. Chance of precipitation is 60%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday:\nTemperature: 56°F\nWind: 3 to 7 mph NE\nForecast: Patchy fog before 11am, then isolated rain showers. Mostly cloudy, with a high near 56. Northeast wind 3 to 7 mph. Chance of precipitation is 20%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nFriday Night:\nTemperature: 46°F\nWind: 3 to 8 mph E\nForecast: A slight chance of rain showers before 10pm, then patchy fog and a slight chance of rain showers. Mostly cloudy, with a low around 46. East wind 3 to 8 mph. Chance of precipitation is 20%.\n\n---\n\nSaturday:\nTemperature: 75°F\nWind: 3 to 10 mph S\nForecast: A chance of rain showers and patchy fog. Partly sunny, with a high near 75. South wind 3 to 10 mph, with gusts as high as 20 mph. Chance of precipitation is 30%.\n"
    },
    "isError": false
  }
}

总结

  1. 调用工具流程方面:Coze Agent调度器先调用 LLM 获取工具调用指令,再调用工具 API,然后将工具结果与对话历史合并,再次调用 LLM ,这个过程循环。而MCP Host调用工具,是与Server通信,通过Server调用工具,并将结果返回MCP Host。当然,这个过程也可以视为MCP Host直接调用Server工具。MCP Host的工具调用结果,与LLM调用的循环逻辑,与Coze相似。

  2. 通信协议方面:Coze 的工具调用通过 HTTP API 实现,响应格式因插件而异,非标准化;MCP 使用统一的 JSON-RPC 协议,请求/响应格式固定(如 tools/call 方法),便于 Host 与任意 Server 交互。

  3. 能力扩展方面:Coze 主要聚焦于工具(插件、工作流等);MCP 协议不仅支持工具,还支持 资源 和 提示词 的发现与调用,为 LLM 应用提供更丰富的上下文能力。这也是,MCP协议命名为模型上下文协议,而不是模型工具协议的原因。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐