使用 MCP 与 A2A 设计多智能体 AI 系统——与 Model Context Protocol(MCP)生态系统集成

在前几章中,我们探讨了如何构建智能体 AI 系统、如何将其与自定义工具集成,以及如何通过不同前端(如命令行界面(CLI)、Slack bot、基于 Chainlit 的 Web UI)与其交互。本章我们将聚焦:如何把你的智能体 AI 框架集成进 Model Context Protocol(MCP)(modelcontextprotocol.io)生态系统。MCP 非常重要!它是一种用于标准化智能体 AI 系统、模型与工具之间交互的协议。我们将先介绍 MCP 及其优势,然后讲解如何构建 MCP server 与 client,最后演示如何把 MCP 集成到我们的智能体 AI 框架 AI-6 中。

本章将涵盖以下主要主题:

  • 什么是 MCP?
  • MCP 架构核心与核心组件
  • 构建 MCP servers
  • 构建 MCP clients
  • 将 MCP 集成到 AI-6 框架中
  • 串起来跑通整体流程

在本章结束时,你将对 MCP、它为何如此出色,以及如何利用它构建能够受益于丰富 MCP server/client 生态的智能体 AI 系统,形成扎实理解。

技术要求

要跟着实践,请参考 README 中的说明:
github.com/PacktPublis...

什么是 MCP?

MCP 由 Anthropic 团队(www.anthropic.com)提出,用于标准化智能体 AI 系统、数据源与外部服务之间的交互。借助 MCP,你可以构建"会说 MCP"的智能体 AI 系统,并且可以自动与任意 MCP server 集成。这意味着你可以用一个远超自定义工具范围的庞大生态工具来扩展 AI 系统能力。对 MCP 工具开发者而言,它体现了"Write once, Run everywhere "的原则。该协议有正式规范(modelcontextprotocol.io/specificati...),并且带版本管理,演进速度很快。

MCP 背后的动机

在 MCP 之前,每个智能体 AI 系统基本都是"各干各的"。如果开发者希望 AI agent 与外部工具(如 API、数据库、专有服务)交互,就必须设计并实现自定义连接器。这些连接器不仅要懂得如何与外部系统通信,还要懂得如何把相关信息以有用、及时的方式注入到 LLM 的上下文中。这类实现通常以工具形式出现,并且与所使用的具体 AI 框架紧耦合。

这种方式很快就变成了维护噩梦:

  • 开发者不得不为每个用例重写上下文注入逻辑
  • 工具与服务提供商不得不写多套集成层来适配不同 AI 系统
  • 工具无法以标准方式声明自身能力,只能依赖不同 LLM provider 之间互不兼容的工具调用规范

结果是:生态变得碎片化、脆弱。工具与特定框架紧耦合,跨项目复用几乎不可能。

MCP 就是为了解决这个问题而创建的。它引入了一个共享协议,标准化工具与资源如何向模型暴露。开发者不再需要为每个集成硬编码自定义逻辑,而是可以实现一个 MCP 兼容的 server,以可预测的方式"发布/声明"自己的能力。相应地,AI 系统只需要实现一个 client 接口,就能从任意 MCP server 动态发现、查询并调用工具与资源。并且,许多主流编程语言都有现成库可用,让实现 MCP server 与 client 变得很容易。

最终效果是:形成一个模块化、可组合的生态,工具可以跨项目复用。

下面我们更深入看看 MCP 的架构与核心组件。

MCP 架构核心与核心组件

MCP 采用 client-server 架构:MCP server 暴露工具与资源,MCP client 消费它们。MCP client 嵌入在 MCP host(即智能体 AI 系统)中,并可以使用来自多个 MCP server 的工具与资源------有的本地(local),有的远程(remote)。本地 stdio MCP server 更快,但如果来自第三方提供商,会带来安全风险;远程 MCP server 则会面临任何远程 API 都会遇到的网络与认证问题。下图展示了 MCP 架构:

图 7.1:MCP 架构

MCP server 与 client 的通信协议有两层:数据层与传输层。

  • 数据层(data layer) :定义基于 JSON-RPC 的 client-server 通信协议,包括生命周期管理、核心原语(工具、资源、prompts)以及通知。
  • 传输层(transport layer) :定义数据交换的通信机制与通道,包括与传输相关的连接建立、消息分帧(framing)以及授权。

下面拆解 MCP 的关键组件。

MCP servers

MCP server(modelcontextprotocol.io/docs/learn/...)是一个程序,它通过 MCP 向基于 LLM 的系统暴露工具(可调用函数)与资源(上下文信息)。它允许任何兼容的 LLM host 应用以可预测、可组合、与传输无关(transport-agnostic)的方式发现、调用并消费这些能力。每个 MCP server 可以支持多个规范版本,实际版本会在 MCP client 与 server 建立连接时通过协商确定(modelcontextprotocol.io/specificati...)。

虽然 MCP server 也可以暴露资源(modelcontextprotocol.io/docs/learn/...),甚至提供 prompt 支持(modelcontextprotocol.io/docs/learn/...),但这里我们聚焦工具(tools)这一块。

资源其实可以作为工具来暴露(例如 list_resources() 与 get_resource() 这类动作),因此不一定需要单独概念。prompt 管理在很大程度上属于应用层关注点,而不应是单个 MCP server 的职责。同样,如果你确实希望 MCP server 提供 prompt 模板,也完全可以暴露一个 get_prompt_template() 工具。在我看来,把资源(你可以把它理解为"穷人版 RAG")和 prompts 加进 MCP 是个错误:它会让协议变复杂、学习曲线更陡。不过你怎么看都行,欢迎你自行探索这些特性。从这里开始,我们只考虑 MCP 的 tools 部分。

AI-6 中与 MCP server 对应的概念是"一组相关工具",例如文件系统工具。但 AI-6 工具是通过 AI-6 框架以 Python 模块方式被发现与导入到智能体系统中的;而 MCP server 则作为独立进程运行(本地或远程)。MCP client 会通过合适的传输协议与每个 MCP server 通信。

一个 AI 系统可以使用多个 MCP server,每个 server 暴露一套不同工具。

注意:AI 系统对每个想使用的 MCP server 都需要一个专用 MCP client。MCP client 负责发现可用工具,并在需要时调用它们。

MCP clients

MCP client 是与 MCP server 交互、发现并调用工具的组件。它们直接嵌入在 MCP host(智能体 AI 系统)中,或者由像 AI-6 这样的框架进一步抽象。每个 MCP client 负责连接到一个 MCP server。MCP 规范并不简单,因此多数 MCP client 会使用实现了协议的库,并通过易用 API 与 MCP server 交互。我强烈建议用库而不是从零实现协议:第一,没必要重复造轮子;自己实现并不会带来多少创新或性能收益。第二,MCP 是个复杂且持续演进的协议:你要先确保自己实现正确,然后还要长期维护并追着规范变化更新------工作量很大且没有收益。

AI-6 中与 MCP client 对应的机制,是引擎的"工具发现"流程:它扫描工具目录,发现实现 Tool 抽象基类的 Python 类,动态导入并实例化;之后引擎就能通过调用 run() 方法来执行任意工具。

MCP hosts

MCP host 是使用 MCP client 从 MCP server 发现并调用工具的智能体 AI 系统。如果 MCP host 使用了支持 MCP 的 AI 框架,那么它甚至不需要"意识到 MCP 的存在"。框架会以 MCP-agnostic 的方式暴露 agentic loop,而智能体系统只需要为想使用的 MCP 工具提供正确配置即可。

我们很快会看到这些组件如何在 AI-6 框架中协同工作。现在先看本地与远程 MCP server 的关键区别。

本地 MCP servers vs 远程 MCP servers

MCP server 可以是本地的,也可以是远程的。本地 MCP server 与 MCP client 运行在同一台机器上;远程 MCP server 则运行在另一台机器上(可能在云端)。选本地还是远程取决于用例与资源条件。

一个 MCP client 可以连接多个 MCP server------既可以本地也可以远程。连接本地 MCP server 时,client 使用标准输入/输出(STDIO)传输(modelcontextprotocol.io/specificati...);连接远程 MCP server 时,client 使用 Streamable HTTP 传输(modelcontextprotocol.io/specificati...)。

注意:以上描述基于当前版本的 MCP 规范(modelcontextprotocol.io/specificati...)。

无论采用何种传输方式,消息序列是一样的,并且消息格式始终是 JSON-RPC 2.0(www.jsonrpc.org/)。

使用 MCP 的收益

对不同角色而言,MCP 为智能体 AI 系统带来多项关键收益。我们按智能体框架、工具提供方与智能体系统开发者来拆解:

  • 面向智能体 AI 框架(MCP for agentic AI frameworks) :支持 MCP 的智能体框架(如 AI-6、LangGraph、AutoGen、CrewAI)可以自动获得对一个巨大且快速增长的可互操作工具与资源生态的访问。在目前阶段,这几乎已经成为智能体框架的基础要求。注意:支持 MCP 并不意味着放弃自定义工具。比如 AI-6 同时支持 MCP 与自定义工具。我们会在本章后面讨论为什么这很重要。

  • 面向工具提供方(MCP for tool providers) :工具提供方通过发布 MCP server,可以自动兼容任何使用支持 MCP 的 AI 框架的 AI 系统。不再需要为每个 AI 框架提供专用 wrapper,也不再需要写多份不同框架的使用文档。工具提供方只需按 MCP 接口实现一次,就能获得广泛兼容性。这为形成可复用、可组合的工具生态打开大门------就像传统软件中的 API 一样。MCP 也更容易定义工具元数据,例如能力描述、安全约束与成本模型。随着 MCP 生态繁荣,工具开发者还可以依赖社区提供 registry、发现机制与其他基础设施。

    MCP 对工具开发者的另一个重大收益是:协议与语言无关,你可以选择任何编程语言。这使得用 Python、TypeScript,甚至 shell 脚本快速编写 MCP 工具成为可能;而如果你要写一个封装关键系统的"重型" MCP server,需要更强控制力或性能优化,则可以选择 Rust 等语言。

  • 面向智能体 AI 系统开发者(MCP for agentic AI system developers) :这就更简单了。作为智能体系统开发者,你应选择一个支持 MCP 的智能体框架(AI-6、LangGraph、AutoGen、CrewAI,以及其他"合格"的智能体框架)。选用 MCP 兼容框架,你就能立刻获得对一个快速增长的可互操作工具生态的访问。

MCP 的这些收益催生了一个非常活跃的生态系统,我们接下来会进一步了解。

MCP 生态系统

MCP 生态正快速成为构建与扩展智能体 AI 系统的基础设施。它标准化了 agents、tools、memory systems 与 orchestration layers 的交互方式,把碎片化的集成变成内聚、可组合的架构。该生态包含不断扩展的 MCP 兼容框架(如 AI-6、LangGraph、AutoGen),以及工具提供方、记忆后端与控制界面。模块化的方法让开发者可以以极低摩擦"混搭"组件,更容易实验、部署并演进复杂多智能体系统。

不存在 vendor lock-in 或兼容性问题:MCP 标准确保任何兼容工具都可接入,框架也可以替换而无需丢失对工具与资源的投入。这对智能体社区来说是革命性的变化:开发者可以专注于用自己喜欢的框架构建应用,同时确信 MCP 工具可以在任何框架中复用。

目前,寻找 MCP servers 的"半官方"入口,是 modelcontextprotocol.io 网站上的 Example Servers 页面(modelcontextprotocol.io/examples),其中列出了不同语言的参考实现,以及多家第三方公司的官方集成。

也有很多社区网站专门索引与列出现有 MCP servers,例如:

在使用一些冷门 MCP server 前要小心,尤其是当你下载并在本地运行它时------它将拥有启动它的那个用户的全部权限。

好,现在我们已经对 MCP 及其优势有了比较全面的理解,接下来我们将看看如何构建 MCP servers 与 clients,以及如何把 MCP 集成进 AI-6 框架。

构建 MCP servers

用不同编程语言与不同库来构建 MCP server,会让你对 MCP 的优势有更直观的理解。你获得的知识、技能,甚至你写出来的 MCP server 本身,都可以迁移到任何现代智能体 AI 系统或框架中。

我们将构建两个 MCP server,它们暴露的功能与 AI-6 的一些自定义工具相似甚至相同,例如文件系统工具与 GitHub 工具。这个过程也让我们能对比两种实现:一种只面向 AI-6,另一种面向 MCP。

文件系统 MCP server

先快速回顾一下 AI-6 的自定义文件系统工具。你还记得,它们基本都是 CommandTool 基类的子类,并遵循相同模式。唯一的例外是 Echo 工具,用于创建新文件:

bash 复制代码
for file in py/backend/tools/file_system/*.py; do
    if [ -f "$file" ]; then
        echo "=== $file ==="
        cat "$file"
        echo
    fi
done
python 复制代码
# === py/backend/tools/file_system/awk.py ===
from backend.tools.base.command_tool import CommandTool

class Awk(CommandTool):
    def __init__(self, user: str | None = None):
        super().__init__(
            command_name='awk', user=user,
            doc_link='https://www.gnu.org/software/gawk/manual/gawk.html')


##=== py/backend/tools/file_system/cat.py ===
from backend.tools.base.command_tool import CommandTool

class Cat(CommandTool):
    def __init__(self, user: str | None = None):
        super().__init__(
            command_name='cat', user=user,
            doc_link = 'https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html')


##=== py/backend/tools/file_system/echo.py ===
import os
import sh
from backend.object_model import Tool, Parameter


class Echo(Tool):
    def __init__(self, user: str | None = None):
        self.user = user

        desc = 'Write content to a file, creating any necessary directories.'
        super().__init__(
            name='echo',
            description=desc,
            parameters=[
                Parameter(
                    name='file_path', type='string',
                    description='The path of the file to write to.'),
                Parameter(
                    name='content', type='string',
                    description='The content to write to the file.')
            ],
            required={'file_path', 'content'}
        )


    def run(self, **kwargs):
        filename = kwargs['file_path']
        content = kwargs['content']
        dir_path = os.path.dirname(filename)

        if self.user is not None:
            sh.sudo('-u', self.user, 'mkdir', '-p', dir_path)
            sh.sudo('-u', self.user, 'tee', filename, _in=content,
                _out=os.devnull)

            return f"Content written to {filename} as user {self.user}"

        os.makedirs(dir_path, exist_ok=True)
        with open(filename, 'w') as file:
            file.write(content)

        return f"Content written to {filename}"


##=== py/backend/tools/file_system/ls.py ===
from backend.tools.base.command_tool import CommandTool

class Ls(CommandTool):
    def __init__(self, user: str | None = None):
        super().__init__(command_name='ls', user=user, doc_link='https://www.gnu.org/software/coreutils/manual/html_node/ls-invocation.html')


##=== py/backend/tools/file_system/patch.py ===
from backend.tools.base.command_tool import CommandTool

class Patch(CommandTool):
    def __init__(self, user: str | None = None):
        super().__init__(
            command_name='patch', user=user,
            doc_link = 'https://www.gnu.org/software/diffutils/manual/html_node/patch-Invocation.html')


##=== py/backend/tools/file_system/pwd.py ===
from backend.tools.base.command_tool import CommandTool

class Pwd(CommandTool):
    def __init__(self, user: str | None = None):
        doc_link = 'https://www.gnu.org/software/coreutils/manual/html_node/pwd-invocation.html'
        super().__init__('pwd', user=user, doc_link=doc_link)


##=== py/backend/tools/file_system/sed.py ===
from backend.tools.base.command_tool import CommandTool

class Sed(CommandTool):
    def __init__(self, user: str | None = None):
        super().__init__(
            command_name='sed', user=user,
            doc_link='https://www.gnu.org/software/sed/manual/sed.html')

我们的文件系统 MCP server 会用等价功能替换其中一些工具。AI-6 内置的 MCP 工具都在 py/backend/mcp-tools 目录下。

我们来看 fs_mcp_server.py 这个 MCP server。它使用官方 MCP Python SDK(github.com/modelcontex...)。FastMCP 类负责完成大量工作:注册工具并通过各种 MCP transport 暴露它们。该模块在完成必要 imports 后实例化 FastMCP:

java 复制代码
import shlex
from mcp.server.fastmcp import FastMCP
import sh

mcp = FastMCP("FileSystem Tools", "")

该文件系统 MCP server 在一个模块里包含多个工具。每个工具实现为一个函数,并用 @mcp.tool() 装饰器标注。类似于 AI-6 里 Tool 抽象基类用来标记"这是个工具类",@mcp.tool() 用来标记"这是个工具函数":

less 复制代码
@mcp.tool()
def ls(args: str) -> str:
    """ls tool. See https://www.gnu.org/software/coreutils/manual/html_node/ls-invocation.html"""
    parsed_args = shlex.split(args)
    return sh.ls(*parsed_args)

@mcp.tool()
def cat(args: str) -> str:
    """cat tool. See https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html"""
    parsed_args = shlex.split(args)
    return sh.cat(*parsed_args)

@mcp.tool()
def pwd(args: str) -> str:
    """pwd tool. See https://www.gnu.org/software/coreutils/manual/html_node/pwd-invocation.html"""
    parsed_args = shlex.split(args) if args.strip() else []
    return sh.pwd(*parsed_args)

@mcp.tool()
def mkdir(args: str) -> str:
    """mkdir tool. See https://www.gnu.org/software/coreutils/manual/html_node/mkdir-invocation.html"""
    parsed_args = shlex.split(args)
    return sh.mkdir(*parsed_args)

@mcp.tool()
def cp(args: str) -> str:
    """cp tool. See https://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html"""
    parsed_args = shlex.split(args)
    return sh.cp(*parsed_args)

可以看到,每个 MCP 工具的实际实现都与 AI-6 的 CommandTool 类非常相似:解析参数,然后用 sh 调用目标命令行工具。下面是 CommandTool 的 run() 方法,它只是额外增加了"以指定用户运行"的能力:

python 复制代码
    def run(self, **kwargs):
        args = shlex.split(kwargs['args'])
        if self.user is not None:
            return sh.sudo('-u', self.user, self.command_name, *args)
        else:
            return getattr(sh, self.command_name)(*args)

对 MCP server 而言,你可以在以进程方式启动 MCP server 时,通过进程级别控制 OS 用户。

简而言之:使用官方 Python SDK 时,用 Python 实现 MCP server 非常简单。注意:如果你需要给 MCP server 做配置,MCP Python SDK 并不会提供任何帮助------你需要自己设计方案,例如用命令行参数、环境变量或配置文件。

不过,有些 MCP server 会更复杂。我们来看看 GitHub MCP server。

GitHub MCP server

GitHub MCP server 用 Bash 实现,有两个原因:第一,展示 MCP server 可以用任何语言编写;第二,更贴近协议本身------因为这里没有库帮你做"重活",你必须直接处理与生成 JSON-RPC 消息,并遵循 MCP 生命周期。

我们一步步拆解。JSON-RPC 消息的标准格式是一个对象,包含 method、id、params 字段。同时,把返回的 JSON 序列化成单行很重要,因为有些 client 会依赖这一点。emit_json() 函数会把换行替换为空格,并处理这一输出细节:

bash 复制代码
#!/bin/bash

# GitHub MCP Server - implements MCP protocol for GitHub CLI tools (newline-stripped JSON responses)

# Helper: output compact JSON from heredoc
emit_json() {
    tr -d '\n' | tr -s ' '
    echo
}

接下来进入主循环:等待 client 发来的 MCP 消息并回应。它会一直运行,解析输入消息并根据不同情况返回响应:

bash 复制代码
# Read JSON-RPC messages from stdin and respond
while read -r line; do
    method=$(echo "$line" | jq -r '.method' 2>/dev/null)
    id=$(echo "$line" | jq -r '.id' 2>/dev/null)
    params=$(echo "$line" | jq -r '.params' 2>/dev/null)

    ...
done

看看它期望接收哪些消息、以及如何响应。每条输入消息都会在一个大的 case 语句里按 method 分支处理。第一条消息是 "initialize":由 MCP client 发起以初始化连接。MCP server 需要返回支持的协议版本与 server 信息;capabilities 部分可以为空:

php 复制代码
    case "$method" in
        "initialize")
            cat <<EOF | emit_json
{
  "jsonrpc": "2.0",
  "id": $id,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},
      "resources": {}
    },
    "serverInfo": {
      "name": "GitHub CLI Tools",
      "version": "1.0.0"
    }
  }
}
EOF
            ;;

client 随后会发送一个通知表示初始化成功。这是 notification,不需要响应:

bash 复制代码
        "notifications/initialized")
            # Notification - no response needed
            ;;

"tools/list" 消息通常只会被调用一次,server 返回其工具列表(这里就一个工具)。工具定义包含熟悉的工具 name、description,以及参数 input schema。我们只有一个必填参数 args,即 GitHub CLI 的命令行参数:

perl 复制代码
        "tools/list")
            cat <<EOF | emit_json
{
  "jsonrpc": "2.0",
  "id": $id,
  "result": {
    "tools": [
      {
        "name": "gh",
        "description": "Execute GitHub CLI commands",
        "inputSchema": {
          "type": "object",
          "properties": {
            "args": {
              "type": "string",
              "description": "GitHub CLI command arguments"
            }
          },
          "required": ["args"]
        }
      }
    ]
  }
}
EOF
            ;;

此时 MCP client 已经知道该 server 提供哪些工具,并可以在需要时调用它们。工具调用是最复杂的一部分:先验证 tool name 是否为 "gh"(唯一支持的工具名);然后执行 gh $args 2>&1 捕获 stdout 与 stderr;记录 exit code;把结果做正确转义;最后构造并返回包含转义结果的 JSON-RPC 响应:

php 复制代码
if [ "$tool_name" = "gh" ]; then
                result=$(gh $args 2>&1)
                exit_code=$?
                escaped=$(echo "$result" | jq -Rs .)

                cat <<EOF | emit_json
{
  "jsonrpc": "2.0",
  "id": $id,
  "result": {
    "content": [
      {
        "type": "text",
        "text": $escaped
      }
    ]
  }
}
EOF

如果 tool name 不是 "gh",就返回错误:

perl 复制代码
else
                cat <<EOF | emit_json
{
  "jsonrpc": "2.0",
  "id": $id,
  "error": {
    "code": -32602,
    "message": "Unknown tool: $tool_name"
  }
}
EOF
            fi        
            ;;

最后,如果 method 未知,则返回相应错误:

perl 复制代码
*)
            if [ "$id" != "null" ]; then
                cat <<EOF | emit_json
{
  "jsonrpc": "2.0",
  "id": $id,
  "error": {
    "code": -32601,
    "message": "Method not found: $method"
  }
}
EOF
            fi
            ;;
    esac
done

这就是我们的低层 Bash MCP server。它写起来工作量很大,而且协议一旦演进,你就得更新 server。并且它只实现了 STDIO transport,因此不能作为远程 MCP server 使用。强烈建议你在构建自己的 MCP server 时使用官方 MCP SDK 之一。

接下来我们看看:构建 MCP client 需要什么。

构建 MCP clients

MCP client 连接到 MCP server 并控制交互。我们不会像在 GitHub Bash MCP server 那样"钻到最底层"从零实现一个 MCP client。相反,我们会利用 MCP Python SDK。AI-6 提供了自己的 MCPClient 封装,它可以连接多个 MCP server,返回它们的工具列表,并在后续调用任意工具。

MCPClient 类

我们的 MCPClient 类位于 mcp_client.py 模块。先看 imports。注意:MCP client 采用异步(async)方式设计。从 mcp 包中,它导入 ClientSessionStdioServerParameter(s) 类,然后再导入用于本地 MCP server 的 stdio_client,以及用于远程 MCP server 的 sse_client

javascript 复制代码
import asyncio
from contextlib import AsyncExitStack
from urllib.parse import urlparse

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.client.sse import sse_client

构造函数很简单:它维护一个 sessions 字典,以及一个 server_tools 字典(把 MCP server 映射到该 server 的工具列表)。此外,它创建一个 async exit stack 以便做正确清理:

python 复制代码
class MCPClient:
    """Standalone MCP client for connecting to and interacting with MCP servers."""
    def __init__(self):
        self.sessions: dict[str, ClientSession] = {}    
        self._server_tools: dict[str, list[dict]] = {}
        self.exit_stack = AsyncExitStack()

当 client 连接到 MCP server 时,会调用 connect_to_server 方法。下面看它怎么工作。

连接到 MCP server

connect_to_server() 是一个 async 方法,做了不少工作。我们分段看。它接收一个 server_id(由 AI 系统控制、用于唯一标识目标的任意 ID)以及一个路径(本地 MCP server)或 URL(远程 MCP server)。如果已经连接过该 server,就直接返回缓存的工具列表:

python 复制代码
    async def connect_to_server(
        self, server_id: str, server_path_or_url: str
    ) -> list[dict]:
        """Connect to a single MCP server and return its tools."""
        if server_id in self.sessions:
            # Already connected, return cached tools
            return self._server_tools.get(server_id, [])

接着判断是本地 server 还是远程 server:

ini 复制代码
        # Check if this is a URL (remote server) or file path (local server)
        parsed = urlparse(server_path_or_url)
        is_url = parsed.scheme in ('http', 'https')

如果是远程 server,就用 Server-Sent Events(SSE)client 创建 transport,并据此生成 ClientSession。出错则打印错误并重新抛异常:

python 复制代码
        if is_url:
            print(
                f"Connecting to remote MCP server: {server_path_or_url}")
            try:
                transport = await self.exit_stack.enter_async_context(
                    sse_client(server_path_or_url))
                read, write = transport
                session = await self.exit_stack.enter_async_context(
                    ClientSession(read, write))
                print(f"Successfully connected to {server_path_or_url}")
            except Exception as e:
                print(f"Failed to connect to remote MCP server {server_path_or_url}: {e}")
                raise

如果是本地 server,它会基于文件扩展名(仅此而已)判断是 Python、TypeScript 还是 Shell MCP server;否则抛异常。这是一个选择:AI-6 目前不打算支持其他语言的本地 MCP server。当然,你完全可以支持更多语言,或者更进一步,支持 Docker 镜像------镜像内部可以包含任意语言实现的 MCP server:

bash 复制代码
        else:
            # Local file-based server
            if server_path_or_url.endswith('.py'):
                command = "python"
            elif server_path_or_url.endswith('.sh'):
                command = "bash"
            elif server_path_or_url.endswith('.js'):
                command = "node"
            else:
                raise ValueError(f"Unsupported server type: {server_path_or_url}")

接着准备 server 参数:实际上就是 MCP server 文件路径。不提供命令行参数,也不提供额外环境变量。未来我们可能希望支持更丰富的配置。随后生成 STDIO transport,并用它创建本地 ClientSession:

ini 复制代码
            server_params = StdioServerParameters(
                command=command,
                args=[server_path_or_url],
                env=None
            )
        
            stdio_transport = await self.exit_stack.enter_async_context(
                stdio_client(server_params))
            stdio, write = stdio_transport
            session = await self.exit_stack.enter_async_context(
                ClientSession(stdio, write))

到这里我们已经拿到了 ClientSession(本地或远程),接下来就可以通过 session.list_tools() 拉取 MCP server 的工具列表。底层会按协议与 MCP server 交换对应 JSON-RPC 消息:

ini 复制代码
        # Initialize session with timeout
        await asyncio.wait_for(session.initialize(), timeout=10.0)

        # List tools with timeout
        response = await asyncio.wait_for(
            session.list_tools(), timeout=10.0)
        tools = [{
            "name": tool.name,
            "description": tool.description,
            "parameters": tool.inputSchema
        } for tool in response.tools]

最后,把 session 与 tools 缓存起来,以便后续使用,并返回工具列表:

php 复制代码
        # Cache the session and tools
        self.sessions[server_id] = session
        self._server_tools[server_id] = tools
    
        return tools

注意:10 秒的 timeout 在多数情况下足够,但少数情况下如果 MCP server 初始化流程很长,最好提供可配置的超时参数。

当智能体系统连上 MCP server 后,下一步就是调用该 server 提供的一个或多个工具。

调用 MCP 工具

智能体系统在调用 connect_to_server() 时,已经获取了每个 MCP server 的工具列表。之后它可以在任何时刻调用其中任意工具:调用 invoke_tool(),传入 server_id、tool_name,以及工具参数字典。该调用会返回工具响应(除非抛异常)。首先它检查 requested server_id 是否存在活跃 session,否则抛异常:

python 复制代码
    async def invoke_tool(
        self, server_id: str, tool_name: str, tool_args: dict
    ) -> str:
        """Invoke a specific tool on the specified MCP server."""
        session = self.sessions.get(server_id)
        if not session:
            raise RuntimeError(f"No active session for server '{server_id}'. Connect to server first.")

然后通过 session.call_tool() 调用工具名与参数,并返回工具响应(可能为空)。如果调用超时或发生其他异常,会打印错误并重新抛出:

python 复制代码
        try:
            print(f"Invoking tool {tool_name} on server {server_id} with args: {tool_args}")
            # Add timeout to prevent hanging on slow/unresponsive servers
            result = await asyncio.wait_for(
                session.call_tool(tool_name, tool_args),
                timeout=30.0  # 30 second timeout
            )
            print(f"Tool {tool_name} completed successfully")
            return result.content[0].text if result.content else ""
        except asyncio.TimeoutError:
            print(f"Tool invocation timed out for {server_id}:{tool_name} after 30 seconds")
            raise RuntimeError(f"Tool invocation timed out for {server_id}:{tool_name} after 30 seconds")
        except Exception as e:
            print(f"Tool invocation failed for {server_id}:{tool_name}: {e}")
            raise

该类还提供一些辅助方法:

get_server_tools():如果智能体系统希望在需要时按需获取某个 server 的工具列表(从缓存中取):

python 复制代码
    def get_server_tools(self, server_id: str) -> list[dict]:
        """Get cached tools for a server."""
        return self._server_tools.get(server_id, [])

is_connected():检查 server_id 是否已有 session:

python 复制代码
    def is_connected(self, server_id: str) -> bool:
        """Check if connected to a server."""
        return server_id in self.sessions

disconnect_server():删除某个 server 的 ClientSession,并移除其工具列表。对于资源消耗较大、只在对话某些阶段需要的 MCP server,这很有用:

python 复制代码
    async def disconnect_server(self, server_id: str):
        """Disconnect from a specific server."""
        if server_id in self.sessions:
            # Session cleanup handled by exit_stack
            del self.sessions[server_id]
            if server_id in self._server_tools:
                del self._server_tools[server_id]

cleanup():清空 sessions 与 _server_tools,并关闭 exit stack:

python 复制代码
    async def cleanup(self):
        """Cleanup all resources."""
        self.sessions.clear()
        self._server_tools.clear()
        await self.exit_stack.aclose()

接下来我们将看看:如何在保留 AI-6 自有"专有工具系统"的同时,为 AI-6 框架增加 MCP 支持。

将 MCP 集成到 AI-6 框架中

AI-6 已经有自己的一套工具系统:包括发现(discovery)、注册(registration)、一个名为 Tool 的抽象基类,以及引擎的整个 agentic loop 都围绕这些概念构建。那么,我们该如何再集成一整套新的工具系统------比如 MCP 呢?我们可以利用软件工程基本定理(FTSE)(en.wikipedia.org/wiki/Fundam...):"我们可以通过引入一层额外的间接层来解决任何问题。" 在这里,这意味着:用一层间接封装(indirection layer)把所有 MCP 工具包装成"看起来像 AI-6 工具"的对象。于是就有了通用的 MCPTool 类:它可以封装一个 MCP 工具(本地或远程),并让 AI-6 像使用自家工具一样访问它。下面看看这套"魔法"是如何发生的。

通用 MCPTool 类

MCPTool 类(github.com/Sayfan-AI/a...)必须是 AI-6 的 Tool 类的子类,也就是说它需要实现 Tool 的抽象方法。这基本就是唯一的兼容性要求。MCPTool 在构造时需要拿到一个 MCPClient 实例,以便当工具的 run() 被调用时,它可以通过该 client 去调用对应的 MCP 工具。我们来看代码。

先看 imports。由于 MCPClient 是基于 asyncio 的异步实现,我们需要 asyncio 与 threading 来处理这些问题;从 AI-6 对象模型里,我们需要 Tool 基类与 Parameter 定义:

javascript 复制代码
import asyncio
import threading

from backend.object_model.tool import Tool, Parameter
from backend.mcp_client.mcp_client import MCPClient

先直接进入最核心也最"棘手"的部分:把 MCP 工具 schema 适配到 AI-6 对象模型。_json_schema_to_parameters() 接收一个 MCP 工具 schema 字典,并把它转换为 AI-6 的 Parameter 对象列表,以及 required 参数名集合。代码本身很直观,因此无需逐行解释:

python 复制代码
def _json_schema_to_parameters(
    schema: dict
) -> tuple[list[Parameter], set[str]]:
    """Convert JSON schema to Tool parameters and required set."""
    parameters = []
    required = set()

    if not isinstance(schema, dict) or 'properties' not in schema:
        return parameters, required

    # Get required fields
    if 'required' in schema and isinstance(schema['required'], list):
        required = set(schema['required'])

    # Convert properties to parameters
    for prop_name, prop_def in schema['properties'].items():
        param_type = prop_def.get('type', 'string')
        description = prop_def.get('description',
            f'{prop_name} parameter')
    
        # Map JSON schema types to our parameter types
        if param_type == 'array':
            param_type = 'array'
        elif param_type in ['integer', 'number']:
            param_type = 'number'
        elif param_type == 'boolean':
            param_type = 'boolean'
        else:
            param_type = 'string'
        
        parameters.append(Parameter(
            name=prop_name,
            type=param_type,
            description=description
        ))

    return parameters, required

我们很快会看到这个函数被用在哪里。继续看 MCPTool 类本身。它有多个类级字段,因为所有 MCPTool 实例共享同一个 MCPClient 对象,而该对象会管理所有 MCP ClientSession。由于 MCPClient 是异步的,这里需要谨慎管理:用 client lock、以及共享 event loop 来避免各种 asyncio 的坑:

python 复制代码
class MCPTool(Tool):
    """Tool that uses MCP (Model Context Protocol) servers."""

    # Shared MCP client instance across all MCP tools
    _client: MCPClient = None
    _client_lock = threading.Lock()
    # Shared event loop for all MCP operations
    _event_loop = None
    _loop_thread = None
    _loop_lock = threading.Lock()

构造函数接收:server_id、server_path_or_url(本地路径或远程 URL,取决于 MCP server 类型),以及从 MCP server 返回的 tool 信息字典。代码会从 tool_info 里取出 name 与 description(或默认值),然后调用 _json_schema_to_parameters() 把 MCP 工具参数转换为 AI-6 参数。接着把这些 AI-6 工具信息交给 Tool 基类(调用 super().init()),并保存 server_id、server_path_or_url、以及 tool name(后面调用时要用):

python 复制代码
    def __init__(
        self, server_id: str, server_path_or_url: str, tool_info: dict
    ):
        """Initialize from MCP tool information."""
        tool_name = tool_info['name']
        description = tool_info.get(
            'description', f'{server_id} tool: {tool_name}')
    
        # Convert parameters from JSON schema
        parameters, required = _json_schema_to_parameters(
            tool_info.get('parameters', {})
        )
    
        super().__init__(
            name=tool_name, description=description,
            parameters=parameters, required=required)
        self.server_id = server_id
        self.server_path_or_url = server_path_or_url
        self.mcp_tool_name = tool_name

你还记得,MCPClient 在类级别管理。_get_client() 类方法会在第一次调用时创建 MCPClient 实例,以后都复用同一个实例。它用的是 double-checked locking 模式(en.wikipedia.org/wiki/Double...),避免重复实例化:

python 复制代码
@classmethod
    def _get_client(cls) -> MCPClient:
        """Get or create the shared MCP client instance."""
        if cls._client is None:
            with cls._client_lock:
                if cls._client is None:
                    cls._client = MCPClient()
        return cls._client

_get_or_create_loop() 采用同样模式:返回(必要时创建)一个单例 asyncio event loop:

python 复制代码
@classmethod
def _get_or_create_loop(cls):
    """Get or create a shared event loop for MCP operations."""
    # Always use our own managed loop for sync tool execution
    # This avoids "event loop is closed" issues from creating/closing loops repeatedly
 
    if cls._event_loop is None or cls._event_loop.is_closed():
        with cls._loop_lock:
            if cls._event_loop is None or cls._event_loop.is_closed():
                cls._event_loop = asyncio.new_event_loop()
             
    return cls._event_loop

_ensure_connected() 会拿到 client 与 event loop,并显式设置 event loop 以避免冲突;如果 client 还没连上该 server,就等待连接完成;最后返回 client 与 loop:

python 复制代码
def _ensure_connected(self):
    """Ensure connection to the MCP server."""
    client = self._get_client()
    loop = self._get_or_create_loop()
    if not client.is_connected(self.server_id):
        # Use shared event loop for connection
        asyncio.set_event_loop(loop)
        loop.run_until_complete(
            client.connect_to_server(
                self.server_id, self.server_path_or_url))
    return client, loop

完成 MCPClient wiring 与各种 asyncio "坑"处理后,再看 run() 方法------当 LLM 请求执行工具调用时,AI-6 引擎就是调用这个 run():

python 复制代码
def run(self, **kwargs) -> str:
    """Execute the MCP tool with the given arguments."""
    client, loop = self._ensure_connected()
    return loop.run_until_complete(
        client.invoke_tool(self.server_id, self.mcp_tool_name, kwargs

它先调用 _ensure_connected() 得到 client 与 event loop。好处是:无需把参数从 AI-6 表示转换成 MCPClient 表示,因为两者本质上都是 str -> object 的字典。随后 MCPClient 的 invoke_tool() 会与 MCP server 交互并以字符串返回结果。

最后,cleanup_all() 类方法用来优雅关闭 asyncio 相关资源:

python 复制代码
@classmethod
def cleanup_all(cls):
    """Cleanup all MCP connections. Call this on shutdown."""
    if cls._client is not None:
        # Use our managed loop for cleanup
        loop = cls._get_or_create_loop()
        asyncio.set_event_loop(loop)
        try:
            loop.run_until_complete(cls._client.cleanup())
        finally:
            # Now we can close our managed loop
            if cls._event_loop and not cls._event_loop.is_closed():
                cls._event_loop.close()
            cls._event_loop = None
        cls._client = None

到这里,我们已经走读完 MCPTool 类,但还缺关键一环:各种 MCP server 是如何被发现并实例化的?

MCP 工具发现(discovery)

在 MCP 集成之前,AI-6 的 Engine 类负责工具发现;但引入 MCP 后,作者把这部分功能抽到了一个专门模块 tool_manager:它负责原生 AI-6 工具发现、本地 MCP 工具发现,以及把配置好的远程 MCP server 也纳入工具集合。

下面我们看看 tool manager 如何工作,以及 Engine 如何通过它获得全部工具的访问能力。

tool manager

tool_manager.py 模块(github.com/Sayfan-AI/a...)把大量复杂性从 Engine 类中剥离出来,让 Engine 更专注于运行 agentic loop 这个核心任务。

先看 imports(现在应该都很熟了)。注意这里使用了 importlib.util 与 inspect,它们承担了动态自定义工具发现(原先在 engine 里)的重活:

javascript 复制代码
import asyncio
import os
from pathlib import Path
import importlib.util
import inspect

from backend.object_model.tool import Tool
from backend.tools.base.mcp_tool import MCPTool
from backend.mcp_client.mcp_client import MCPClient
from backend.engine.config import ToolConfig

get_tool_dict() 是唯一的公开函数。所有工具(无论是 AI-6 原生还是 MCP 工具)的发现都发生在这里。它从空列表开始,然后调用以下函数获取不同类型工具并合并:

  • _discover_native_tools()
  • _discover_local_mcp_tools()
  • _get_remote_mcp_tools()

所有工具都继承自 Tool 基类,引擎知道如何配置并调用它们:

python 复制代码
def get_tool_dict(tool_config: ToolConfig) -> dict[str, Tool]:
    """Get a dictionary of all available tools from various sources.

    Args:
        tool_config: ToolConfig object containing tool configuration

    Returns:
        Dict mapping tool names to Tool instances
    """
    tools: list[Tool] = []

    # 1. Discover AI-6 native tools
    if tool_config.tools_dir:
        native_tools = _discover_native_tools(
            tool_config.tools_dir,
            tool_config.tool_config
        )
        tools.extend(native_tools)

    # 2. Discover local MCP tools
    if tool_config.mcp_tools_dir:
        local_mcp_tools = _discover_local_mcp_tools(
            tool_config.mcp_tools_dir
        )
        tools.extend(local_mcp_tools)

    # 3. Get tools of remote MCP servers
    if tool_config.remote_mcp_servers:
        remote_mcp_tools = _get_remote_mcp_tools(
            tool_config.remote_mcp_servers
        )
        tools.extend(remote_mcp_tools)

    return {tool.name: tool for tool in tools}

我们跳过 _get_local_tools()(第 4 章已经深入讲过),重点看 MCP 工具发现。

本地 MCP 工具发现:_discover_local_mcp_tools()

_discover_local_mcp_tools() 接收一个本地 MCP server 目录,返回 MCPTool 对象列表(也就是:把所有 MCP server 的所有 MCP tools 合并后,用 MCPTool 进行封装)。如果目录不合法则返回空列表:

python 复制代码
def _discover_local_mcp_tools(mcp_servers_dir: str) -> list[MCPTool]:
    """Discover MCP tools dynamically by connecting to MCP servers."""
    if not os.path.isdir(mcp_servers_dir):
        return []

然后定义一个嵌套的 async 函数 discover_async()(只在该函数内部可见),用于桥接同步与异步。拆解如下:

先创建空 tools 列表与一个新的 MCPClient;然后进入 try 块,遍历 MCP server 目录下所有文件:

ini 复制代码
async def discover_async():
    tools: list[Tool] = []
    client = MCPClient()

    try:
        # Iterate over all files in the directory
        for file_name in os.listdir(mcp_servers_dir):

对每个条目,计算脚本路径(本地 MCP server 只支持 Python/Node/Shell)。如果不是文件就跳过:

ini 复制代码
script_path = os.path.join(mcp_servers_dir, file_name)

# Check if it's a file
if not os.path.isfile(script_path):
    continue

如果是文件,则使用文件名作为 server_id 来调用 client.connect_to_server()(返回该 server 的全部工具)。对每个 tool_info 创建一个 MCPTool 实例并加入 tools。若连接失败则打印 warning 并继续处理下一个 server:

python 复制代码
try:
    # Connect to server and get its tools with timeout
    server_tools = await asyncio.wait_for(
        client.connect_to_server(script_path, script_path),
        timeout=5.0
    )

    # Create MCPTool instances for each tool
    for tool_info in server_tools:
        mcp_tool = MCPTool(script_path, script_path, tool_info)
        tools.append(mcp_tool)

except Exception as e:
    # Skip servers that fail to connect or timeout
    error_msg = str(e) if str(e) else f"{type(e).__name__}: {e}"
    print(f"Warning: Failed to connect to MCP server {script_path}: {error_msg}")
    continue

处理完全部 MCP server 后,在 finally 中清理 client,然后返回 tools:

csharp 复制代码
finally:
    await client.cleanup()

return tools

上述逻辑都在 discover_async() 里完成。外层 _discover_local_mcp_tools() 通过 asyncio.run() 运行该 async 函数,从同步上下文中等待其完成并返回结果(合并后的工具列表)。若出错则打印 warning 并返回空列表:

python 复制代码
    # Run async discovery in sync context
    try:
        return asyncio.run(discover_async())
    except Exception as e:
        print(f"Warning: MCP tool discovery failed: {e}"
        return []

远程 MCP 工具接入:_get_remote_mcp_tools()

本地 MCP server 处理完后,还要把远程 MCP server 加进来。_get_remote_mcp_tools() 沿用同样"嵌套 async 函数"的蓝图,但也有关键差异,我们详细过一遍。

它接收一个字典列表,每个字典包含远程 MCP server 的 name 与 url,因此不需要扫描目录寻找 server。返回的 Tool 列表初始化为空:

python 复制代码
def _get_remote_mcp_tools(remote_servers: list[dict]) -> list[Tool]:
    """Connect to remote MCP servers and get their tools.

    Args:
        remote_servers: List of remote server configurations
        Each server config should have: {'url': 'https://...', 'name': '...'}

    Returns:
        List of MCPTool instances for remote tools
    """
    tools: list[Tool] = []

嵌套的 connect_async() 与本地 discovery 的 discover_async() 类似,但这里不会遍历目录文件,而是遍历 remote_servers 配置:逐个连接、拉取工具并创建 MCPTool。此处 server_id 使用配置中的 server_name:

python 复制代码
    async def connect_async():
        client = MCPClient()

        try:
            for server_config in remote_servers:
                try:
                    server_url = server_config.get('url')
                    server_name = server_config.get('name', server_url)

                    if not server_url:
                        print(f"Warning: Remote MCP server config missing 'url': {server_config}")
                        continue

                    # Connect to remote server with timeout
                    server_tools = await asyncio.wait_for(
                        client.connect_to_server(server_name, server_url),
                        timeout=10.0
                    )

                    # Create MCPTool instances for each remote tool
                    for tool_info in server_tools:
                        # Use server_url as script_path for remote tools
                        mcp_tool = MCPTool(
                            server_name, server_url, tool_info)
                        tools.append(mcp_tool)

                except Exception as e:
                    print(f"Warning: Failed to connect to remote MCP server {server_config}: {e}")
                    continue

        finally:
            await client.cleanup()

然后与前面一样运行嵌套函数并返回 tools:

python 复制代码
# Run async connection in sync context
    try:
        asyncio.run(connect_async())
    except Exception as e:
        print(f"Warning: Remote MCP server connection failed: {e}")
        return []

    return tools

至此,我们完成了对 tool_manager 模块的深入拆解。下面看看 Engine 如何受益于这种关注点分离(separation of concerns)。

Engine 如何使用 tool manager

现在引擎里与工具发现相关的 import 只剩一条:

javascript 复制代码
from backend.engine import tool_manager

并且在构造函数里也只剩一行代码调用 tool_manager.get_tool_dict():

ini 复制代码
self.tool_dict = tool_manager.get_tool_dict(tool_config)

这已经简化到极致,同时也提升了 engine 乃至整个 AI-6 项目的架构质量与可维护性。

下面是 Engine 类完整构造函数(现在完全不包含工具发现逻辑):

ini 复制代码
class Engine:
    def __init__(self, config: Config):
        self.default_model_id = config.default_model_id

        # Store threshold ratio and calculate token threshold based on default model
        self.summary_threshold_ratio = config.summary_threshold_ratio
        context_window_size = get_context_window_size(
            self.default_model_id)
        self.token_threshold = int(
            context_window_size * config.summary_threshold_ratio
        )

        # Find LLM providers directory
        llm_providers_dir = os.path.join(
            os.path.dirname(os.path.dirname(__file__)), "llm_providers"
        )
        assert os.path.isdir(llm_providers_dir), (
            f"LLM providers directory not found: {llm_providers_dir}"
        )

        # Discover available LLM providers
        self.llm_providers = Engine.discover_llm_providers(
            llm_providers_dir, config.provider_config
        )
        if not self.llm_providers:
            raise ValueError("No LLM providers found or initialized")
        self.model_provider_map = {
            model_id: llm_provider
            for llm_provider in self.llm_providers
            for model_id in llm_provider.models
        }

        # Discover and initialize all tools
        tool_config = ToolConfig.from_engine_config(config)
        self.tool_dict = tool_manager.get_tool_dict(tool_config)

        # Initialize session and session manager
        self.session_manager = SessionManager(config.memory_dir)
        self.session = Session(config.memory_dir)

        # Session-related attributes
        self.checkpoint_interval = config.checkpoint_interval
        self.message_count_since_checkpoint = 0

        # Instantiate the summarizer with the first LLM provider
        self.summarizer = Summarizer(self.llm_providers[0])

        # Register memory tools with the engine
        self._register_memory_tools()

        # Load previous session if session_id is provided and exists
        if config.session_id:
            available_sessions = self.session_manager.list_sessions()
            if config.session_id in available_sessions:
                self.session = Session(config.memory_dir)  # Create a new session object
                self.session.load(config.session_id)  # Load from disk

好,我们已经覆盖了 MCP 集成到 AI-6 的所有方面。下面看看它如何实际跑起来。

串起来跑通:Putting it all together

我们启动一段简短的 AI-6 session,看看新的 MCP 工具如何工作。我让 AI-6 找出我最受欢迎的 GitHub 仓库。最终结果如下:

图 7.2:AI-6 通过 MCP 访问 GitHub

这是一次巨大成功!LLM 通过我们的 MCP server 访问了 GitHub,提取了所需信息(这也需要认证),并整理出有用答案。

但它也尝试了不少次,因为 LLM 不断请求无效的 gh CLI 分页命令。最后它干脆把 limit 设为 300。对这里的场景可行(我只有一百多个 repo),但不是通用方案。不过你可以看到,它确实在使用 GitHub MCP server(Bash 从零实现):

bash 复制代码
Invoking tool gh on server /Users/gigi/git/ai-six/py/backend/mcp_tools/github_mcp_server.sh with args: {'args': 'repo list --sort=stars --limit=5'}
Tool gh completed successfully
2025-08-03 17:11:59 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
Invoking tool gh on server /Users/gigi/git/ai-six/py/backend/mcp_tools/github_mcp_server.sh with args: {'args': 'repo list --json "name,stargazersCount" --limit 100'}
Tool gh completed successfully
2025-08-03 17:12:00 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
Invoking tool gh on server /Users/gigi/git/ai-six/py/backend/mcp_tools/github_mcp_server.sh with args: {'args': 'repo list --json name,stargazerCount --limit 100'}
Tool gh completed successfully
2025-08-03 17:12:04 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-08-03 17:14:02 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-08-03 17:14:18 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
Invoking tool gh on server /Users/gigi/git/ai-six/py/backend/mcp_tools/github_mcp_server.sh with args: {'args': 'repo list --json name,stargazerCount --limit 100 --page 2'}
Tool gh completed successfully
2025-08-03 17:14:25 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
2025-08-03 17:15:12 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
Invoking tool gh on server /Users/gigi/git/ai-six/py/backend/mcp_tools/github_mcp_server.sh with args: {'args': 'repo list --json name,stargazerCount --limit 300'}
Tool gh completed successfully
2025-08-03 17:15:18 - HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"

这种不正确且低效的工具使用,是 LLM 的主要问题之一,我们会在第 10 章解决它。

总结

回顾一下:本章我们深入探讨了 Model Context Protocol(MCP)------它是高级智能体 AI 系统中实现工具与上下文集成标准化的基石。我们介绍了 MCP 的生态与动机,覆盖了其架构、收益、协议分层与实践实现。我们展示了 MCP server/client 概念如何与 AI-6 框架自身的工具抽象自然映射,并说明集成 MCP 如何带来广泛且低摩擦的互操作性:任何兼容工具都能被连接、复用或替换,从而摆脱 vendor lock-in 与自定义集成的维护成本。

我们对比了将命令行工具实现为 AI-6 自定义工具与实现为 MCP server 两种方式,并看到 MCP 让跨语言、跨平台构建与分享工具同样容易。需要注意的是:测试 MCP server 往往需要额外工作,因为工具运行在独立进程中。你学习了本地与远程 MCP server 如何被发现与消费,以及 AI-6 的 tool manager 如何抽象掉发现与注册的全部过程。这个方案让 AI-6(以及任何类似的智能体框架)能够把内部工具与外部 MCP 工具无缝组合成统一、动态的工具集,任何 agent 都可访问,而 AI-6 引擎无需额外逻辑。

最后我们看了一个真实落地的集成:智能体系统(AI-6)如今把工具处理(发现、调用与管理)全部委派给健壮的 tool manager,使得 agent 的可扩展性大幅提升。随着 MCP 生态持续增长,你的智能体 AI 可以立刻利用任何新发布到该开放协议的工具、服务或资源,从而在复杂多智能体系统中实现快速实验与规模化扩展。集成只需要配置,而不是重写代码。

下一章我们将更进一步,深入多智能体 AI 系统的设计。

相关推荐
数据智能老司机2 小时前
使用 MCP 与 A2A 设计多智能体 AI 系统——构建一个基于工具的智能体 AI 框架
llm·agent
TechFind2 小时前
用 OpenClaw 搭建企业微信 AI Agent:从零到自动化客服只需 30 分钟
人工智能·agent
数据智能老司机2 小时前
使用 MCP 与 A2A 设计多智能体 AI 系统——理解 AI 智能体如何工作
llm·agent
Baihai_IDP6 小时前
回头看 RLHF、PPO、DPO、GRPO 与 RLVR 的发展路径
人工智能·llm·强化学习
智泊AI6 小时前
一口气讲清:AI Agent 八大核心概念,建议收藏!
llm
Sailing6 小时前
LLM 调用从 60s 卡死降到 3s!彻底绕过 tiktoken 网络阻塞(LangChain.js 必看)
前端·langchain·llm
松灵机器人1 天前
【养个小龙虾】让小龙虾帮我控制七轴臂
agent
阿里云大数据AI技术1 天前
用 SQL 调大模型?Hologres + 百炼,让数据开发直接“对话”AI
sql·llm
量子位1 天前
这届MWC真成了中国AI主场,小米直接把AI从对话框里拽出来接管物理世界了
llm·aigc