2024 年 11 月,Anthropic 开源了 MCP(Model Context Protocol),当时多数人把它当作又一个"XX 协议"——每年 AI 圈都会冒出几个协议标准,大部分活不过三个月。一年半后的今天,MCP 的 GitHub 仓库已经积累了数万 Star,Claude Desktop、WorkBuddy、Cursor 等主流工具全面支持,社区贡献的 MCP Server 覆盖了数据库、文件系统、浏览器、API 网关等几乎所有常见场景。甚至有文章将 MCP、A2A、ACP 并称为 Agent 互操作领域的"三大协议",讨论它们之间的竞争与融合。

这一切发生得太快。快到很多人还没搞清楚 MCP 到底是什么、解决了什么问题、和已有的 Function Calling 有什么区别,它就已经成为 AI 基础设施的一部分了。

这篇文章的目的是把 MCP 讲透——不是产品功能介绍,而是从协议设计者的视角出发,拆解它的架构、抽象层次和设计取舍。读完你会理解:为什么 MCP 有资格被称为 AI Agent 时代的 USB-C,以及这个类比背后真正的技术内涵。

01 问题起源:AI Agent 时代的接口巴别塔

要理解 MCP 解决了什么问题,先回到 MCP 诞生之前的世界。

2023 年到 2024 年初,各种 LLM 应用和 Agent 框架如雨后春笋般涌现。每个框架都在做同一件事——让模型能够调用外部工具。但实现方式千奇百怪:

  • OpenAI 定义了 Function Calling 的 JSON Schema 规范,模型输出特定格式的 JSON,应用层解析后执行对应函数
  • LangChain 封装了自己的 Tools 抽象,用 @tool 装饰器注册函数,内部做了一层序列化
  • Claude 在 API 层直接支持 Tool Use,格式与 OpenAI 类似但细节不同
  • 自研 Agent 框架则各搞一套——有的用 YAML 配置工具,有的用 Python 类继承,有的用 gRPC 远程调用

更麻烦的是,每一套工具集成都是紧耦合的。给 LangChain 写的工具只能给 LangChain 用;为 Claude Desktop 写的插件没法迁移到别的平台;企业内部的数据查询接口要分别适配三个 Agent 平台,维护三套几乎相同的代码。

这就是经典的"接口巴别塔"问题。每个基建方都在造自己的接口标准,不是因为大家技术能力不够,而是因为缺少一个被广泛认可的公共协议

MCP 不是重新发明工具调用。它做的事情更底层:定义了一套 Client-Server 通信协议,让工具提供方(Server)和工具消费方(Client/Host)可以解耦开发。

下面这张图概括了 MCP 的核心价值——把 N×M 的适配矩阵变成 N+M:

MCP 之前:N×M 适配

自定义适配

自定义适配

自定义适配

自定义适配

自定义适配

自定义适配

Claude Desktop

数据库

LangChain Agent

自研Agent

文件系统

MCP 之后:标准协议

MCP Client

MCP Client

MCP Client

MCP Server

MCP Server

MCP Server

Claude Desktop

MCP Protocol

LangChain Agent

自研Agent

数据库

文件系统

GitHub API

一句话总结:工具只需实现一次 MCP Server,所有支持 MCP 的 Host 都能即插即用。 这就是 USB-C 类比的含义。USB-C 的价值不在于它比 Micro-USB 多几个引脚,而在于它统一了显示器、充电器、数据线、外设的物理接口,让"兼容性"从需要反复确认的事变成理所当然的事。MCP 在 AI 工具集成领域想做同样的事情。

02 MCP 的架构设计:为什么是 Client-Server?

理解了"why",接下来看"how"——MCP 的架构选了哪条路,以及为什么选这条路。

2.1 整体架构

MCP 的架构可以用三个角色概括:

管理多个 Client

管理多个 Client

stdio / SSE

stdio / SSE

暴露

暴露

暴露

暴露

MCP Host
AI 应用主程序

MCP Client

MCP Client

MCP Server
文件系统

MCP Server
数据库查询

Tools
read_file / write_file

Resources
file://docs/*

Tools
query_database

Resources
db://schemas/*

  • MCP Host:用户实际使用的 AI 应用,如 Claude Desktop、WorkBuddy、Cursor。它负责管理多个 MCP Client 实例,将 MCP Server 的能力呈现给模型。
  • MCP Client:运行在 Host 内部,与一个特定的 MCP Server 维持一对一的连接。Client 负责序列化请求、管理连接生命周期、处理错误重连。
  • MCP Server:独立运行的服务进程,对外暴露 Tools、Resources、Prompts 三种能力。Server 不需要知道是谁在调用它,只需要实现 MCP 协议规范。

这种架构的关键设计选择有两点:

第一,Server 是独立进程,不是库。 这意味着 MCP Server 可以用任何语言实现(Python、TypeScript、Go、Rust…),只要它能通过 stdio 或 HTTP 与 Client 通信。你公司已有的 Go 微服务、Python 脚本、Rust CLI 工具,都能包装成 MCP Server,不需要重写。

第二,Client 与 Server 是一对一的关系,但 Host 管理多个 Client。 这意味着不同的能力由不同的 Server 提供,互不干扰。数据库查询崩溃不会影响文件读写,权限隔离天然实现。

2.2 传输层设计:stdio 优先,HTTP 补充

MCP 支持两种传输方式,有明确的定位差异:

维度 stdio Streamable HTTP (SSE)
进程模型 Server 作为子进程启动 Server 独立运行,Client 主动连接
适用场景 本地工具、桌面应用 远程服务、微服务架构
延迟 极低(进程间通信) 网络延迟
安全性 天然进程隔离 需要额外的鉴权机制
复杂度 简单,无需网络配置 需要配置端口、CORS、鉴权
典型用例 本地文件操作、CLI 工具 企业内部 API 网关、数据库代理

stdio 优先是一个务实的决策。大多数开发者的 MCP Server 运行在本地机器上,不需要网络开销和安全配置。启动一个子进程、通过标准输入输出传递 JSON-RPC 消息,是最简单也最可靠的方案。

// MCP 使用 JSON-RPC 2.0 作为消息格式
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/home/user/document.md"
    }
  }
}

需要远程场景时,切换到 Streamable HTTP 也很简单——MCP 在协议层做了传输无关的设计,Server 开发的业务逻辑代码完全不需要修改。

2.3 生命周期管理

MCP 的连接生命周期分为三个阶段,每个阶段都有明确的协议消息:

┌──────────────────────────────────────────────────┐
│                 MCP 连接生命周期                     │
│                                                    │
│  1. 初始化阶段 (Initialization)                     │
│     Client ── initialize ──► Server                │
│     Client ◄── capabilities ── Server              │
│     Client ── initialized ──► Server               │
│                                                    │
│  2. 运行阶段 (Operation)                            │
│     Client ── tools/list ──► Server                │
│     Client ◄── tool list ── Server                 │
│     Client ── tools/call ──► Server                │
│     Client ◄── result ── Server                    │
│     Client ── resources/read ──► Server            │
│     Client ◄── content ── Server                   │
│                                                    │
│  3. 关闭阶段 (Shutdown)                             │
│     Client/Host 终止进程或关闭连接                    │
│                                                    │
└──────────────────────────────────────────────────┘

初始化阶段,Client 和 Server 互相告知自己的能力和协议版本。这让 MCP 具备了前向兼容的基础——未来的 MCP 版本如果 Server 不支持,Client 可以在初始化时就检测到,优雅降级而不是运行时崩溃。

03 三层抽象深度拆解

MCP 的核心设计精华,浓缩在它向外暴露的三个原语中:Tools、Resources、Prompts。这三个概念构成了 MCP Server 的"能力界面",决定了模型能做什么、能读到什么、能以什么方式交互。

3.1 Tools:让模型「动手」

Tools 是 MCP 最核心的抽象。一个 Tool 本质上是一个模型可通过结构化参数调用、并获得结构化返回值的函数。它的定义包含三个关键部分:

  • name:唯一标识符,模型用它来指定"我要调用哪个工具"
  • description:自然语言描述,模型根据它来判断"这个工具是不是我需要的"
  • inputSchema:JSON Schema 格式的参数定义,告诉模型"这个工具需要哪些参数、每个参数的类型和含义"

以下是一个完整的 MCP Tool 定义示例——一个 SQLite 数据库查询工具:

# MCP Server 端的 Tool 定义
{
    "name": "query_database",
    "description": "在 SQLite 数据库中执行 SELECT 查询,返回结果。"
                   "仅支持 SELECT 语句,不支持 INSERT/UPDATE/DELETE 等写操作。",
    "inputSchema": {
        "type": "object",
        "properties": {
            "sql": {
                "type": "string",
                "description": "要执行的 SQL SELECT 查询语句"
            },
            "params": {
                "type": "array",
                "items": {"type": "string"},
                "description": "SQL 查询的参数列表,按顺序对应 ? 占位符"
            }
        },
        "required": ["sql"]
    }
}

这里有一个重要的设计细节:description 字段不仅仅给人看,更是模型做决策的关键依据。 模型的工具选择逻辑大致是这样的——收到用户指令后,模型会浏览所有可用 Tool 的 name 和 description,用语义匹配来决定调用哪个。description 写得模糊,模型就可能选错工具;description 写得精确,模型的决策质量会显著提升。

一个常见的误区是把 description 写得过于简短(如 “Query database”),导致模型在多个选项之间犹豫不决,反复尝试错误工具。好的 description 应当包含:工具做什么、什么情况下用、有什么限制、返回什么

Tool 调用的完整流程如下:

MCP Server MCP Client MCP Host LLM 模型 用户 MCP Server MCP Client MCP Host LLM 模型 用户 "帮我查过去7天的订单总额" 决定调用 query_database 工具 tools/call {"method":"tools/call","params":{"name":"query_database","arguments":{"sql":"SELECT SUM(amount) FROM orders WHERE date >= ?", "params": ["2026-06-27"]}}} 执行 SQL,验证安全性 {"result":"125,800.00"} 返回结果 将结果注入上下文 "过去7天的订单总额为 ¥125,800"

注意这个流程中的关键节点:Server 收到调用请求后,有完整的自主权来决定是否执行、如何执行。 它可以在执行前做安全检查(SQL 语句是否包含危险操作)、做限流(一分钟最多调用多少次)、做审计日志。MCP 没有把安全责任推给 Host,而是让 Server 成为安全的第一道防线。

3.2 Resources:让模型「查阅」

如果说 Tools 是模型的"手"——执行操作、改变状态——那么 Resources 就是模型的"眼"——读取数据、获取上下文。

Resource 的定位和 Tool 有本质区别:

维度 Tool Resource
语义 执行操作,产生副作用 读取数据,无副作用
调用方式 模型主动选择调用 模型或 Host 主动拉取
参数 JSON Schema 定义结构化参数 URI 模板定义资源地址
典型用例 发送邮件、查询数据库、创建文件 读取文档、获取 schema、查看日志
幂等性 不保证 保证幂等

Resources 使用 URI 来标识。MCP 支持两种 Resource 访问模式:

静态 Resource:URI 固定,内容固定。例如 config://app-settings 指向应用的运行时配置,每次读取返回相同的数据结构。

动态 Resource(URI 模板):URI 包含参数。例如 docs://users/{user_id},传入不同的 user_id 返回不同用户的文档。MCP 使用 RFC 6570 URI 模板标准:

{
  "uri": "db://tables/{table_name}/schema",
  "name": "数据库表结构",
  "description": "返回指定数据表的结构信息,包括列名、类型、约束",
  "mimeType": "application/json"
}

模型或 Host 在需要时可以用 resources/read 方法拉取这个资源:

// 请求
{
  "method": "resources/read",
  "params": {
    "uri": "db://tables/orders/schema"
  }
}

// 响应
{
  "contents": [{
    "uri": "db://tables/orders/schema",
    "mimeType": "application/json",
    "text": "{\"table\":\"orders\",\"columns\":[{\"name\":\"id\",\"type\":\"INTEGER\"},{\"name\":\"amount\",\"type\":\"REAL\"},{\"name\":\"date\",\"type\":\"TEXT\"}]}"
  }]
}

Resources 的设计巧妙之处在于它是拉取模型而非推送模型。Server 不去猜测模型需要什么数据,而是把数据编目好、用 URI 组织好,让模型和 Host 按需来取。这避免了 Context Window 被无用的数据占据,同时保持了最大的灵活性。

3.3 Prompts:让模型「懂得怎么问」

Prompts 是三层抽象中最容易被忽视的一个,但它的设计意图值得仔细揣摩。

Prompts 是 预定义的、参数化的对话模板,存储在 Server 端。当 Host 通过 prompts/list 发现 Server 提供了某些 Prompt 模板后,可以将其展示给用户或模型作为"快捷入口"。

{
  "name": "code_review",
  "description": "对此代码文件进行代码审查,关注安全问题、性能问题和最佳实践",
  "arguments": [
    {
      "name": "language",
      "description": "编程语言",
      "required": true
    },
    {
      "name": "code",
      "description": "要审查的代码内容",
      "required": true
    }
  ]
}

当用户选择这个 Prompt 模板并填入参数后,Server 返回完整的提示文本,Host 将其注入模型的上下文:

你是一位资深 {language} 代码审查专家。请认真审查以下代码:

{code}

请从以下维度进行分析:
1. 安全漏洞(SQL 注入、XSS、权限问题等)
2. 性能问题(不必要的循环、重复查询、内存泄漏)
3. 最佳实践偏离(命名规范、错误处理、代码结构)
4. 可维护性问题(过长的函数、过深的嵌套、重复代码)

对每个问题,请提供具体的代码行号和修改建议。

Prompts 解决了一个微妙但广泛存在的问题:用户和模型之间的"需求翻译"鸿沟。 一个有经验的 Prompt Engineer 知道怎么措辞能拿到最好的结果,但普通用户不一定会。把经过验证的 Prompt 模板嵌入 Server 端,等于让工具提供方把"怎么用好这个工具"的知识也一并打包了。

从架构角度看,Prompts 不是 MCP 中最关键的部分——即使没有 Prompts,只靠 Tools 和 Resources 也能构建完整功能。但它体现了 MCP 设计者的一种态度:协议不应该只关心"能做什么",还应该关心"怎么做更好"。

04 三足鼎立:MCP 与 A2A、ACP 的定位与关系

聊完 MCP 内部的设计,必须把镜头拉远,看看 2025-2026 年整个 Agent 协议领域的全景图。

4.1 三大协议速览

维度 MCP A2A ACP
提出方 Anthropic Google 社区/多厂商
核心场景 AI 应用 ↔ 外部工具/数据 Agent ↔ Agent 协作 Agent 通信基础设施
类比 USB-C(外设接口) HTTP REST(服务间通信) TCP/IP(通信底层)
抽象层次 工具调用 + 数据访问 任务委托 + 结果交付 消息路由 + 身份认证
当前状态 生产可用,生态蓬勃 规范草案,早期落地 讨论阶段
传输层 stdio / HTTP SSE HTTP + JSON 待定

4.2 核心差异:纵向集成 vs 横向协作

MCP 和 A2A 的核心差异可以用一个简单的类比来理解:

MCP 解决的是「人 + AI」如何访问工具和数据的问题。 它关注的是垂直方向的整合——一个 AI 应用(Host)如何向下调用 N 个外部能力(MCP Server)。就像 USB-C 统一了电脑和外设的连接方式,MCP 统一了 AI 应用和外部工具的连接方式。

A2A 解决的是「AI + AI」如何协作的问题。 它关注的是水平方向的协作——多个 Agent 之间如何发现彼此、委托任务、交换结果。就像 HTTP REST 统一了微服务之间的通信方式,A2A 试图统一 Agent 之间的通信方式。

MCP(纵向集成)                    A2A(横向协作)
                                    
  AI Application                    
      │                              
      ├── MCP ── 数据库              Agent A ── A2A ── Agent B
      ├── MCP ── 文件系统            (任务委托)    (执行并返回)
      ├── MCP ── GitHub API          
      └── MCP ── 邮件服务            Agent A ── A2A ── Agent C
                                     (查询)        (提供数据)

这两种场景不矛盾,反而是互补的。一个复杂的 Agent 系统很可能同时需要 MCP 和 A2A:MCP 让每个 Agent 能调用工具、访问数据;A2A 让多个 Agent 能协作完成复杂任务。 两者解决的是不同层次的问题。

4.3 不是战争,是生态收敛

2026 年初有一些公众号文章渲染"A协议大战""Agent 协议三国杀"的叙事,这种说法有一些误导性。MCP、A2A、ACP 不是互相竞争的替代品,而是在不同层面解决不同问题的互补协议。更准确的描述是:Agent 互操作协议生态正在从「百花齐放」进入「关键玩家浮出水面」的收敛期。

对于开发者来说,务实的选择是:

  • 如果你在做 AI 应用的工具集成——关注 MCP,它是当前最成熟的方案
  • 如果你在做多 Agent 系统的协作——关注 A2A,但可以先观望其成熟度
  • 如果只是内部项目,先用 MCP 把单 Agent 的工具集成做好,多 Agent 协作的需求通常没那么早到来

05 实战:30 分钟构建一个 SQLite MCP Server

理论讲再多,不如动手写一个。下面用 Python 实现一个功能完整的 SQLite MCP Server,支持查询和表结构浏览。

5.1 项目结构

mcp-sqlite-server/
  server.py        # MCP Server 主程序
  pyproject.toml   # 项目配置

5.2 完整实现

# server.py
"""
SQLite MCP Server
提供数据库查询(SELECT)和表结构浏览能力。
使用 stdio 传输,实现标准的 MCP 协议。
"""

import json
import sys
import sqlite3
from typing import Any


class SQLiteMCPServer:
    """SQLite MCP Server - 实现 tools/list, tools/call, resources/list, resources/read"""

    def __init__(self, db_path: str = "data.db"):
        self.db_path = db_path

    def handle_request(self, request: dict) -> dict | None:
        """处理 JSON-RPC 请求,返回响应或 None(通知消息不返回)"""
        method = request.get("method", "")
        req_id = request.get("id")
        params = request.get("params", {})

        # 路由到对应处理器
        handlers = {
            "initialize": self.handle_initialize,
            "tools/list": self.handle_tools_list,
            "tools/call": self.handle_tools_call,
            "resources/list": self.handle_resources_list,
            "resources/read": self.handle_resources_read,
        }

        if method in handlers:
            try:
                result = handlers[method](params)
                return self._response(req_id, result)
            except Exception as e:
                return self._error(req_id, -32000, str(e))

        # ping 通知不需要回复
        if method == "notifications/initialized":
            return None

        return self._error(req_id, -32601, f"Unknown method: {method}")

    def handle_initialize(self, params: dict) -> dict:
        """初始化握手:声明 Server 能力"""
        return {
            "protocolVersion": "2024-11-05",
            "capabilities": {
                "tools": {},
                "resources": {},
            },
            "serverInfo": {
                "name": "sqlite-mcp-server",
                "version": "1.0.0",
            },
        }

    def handle_tools_list(self, params: dict) -> dict:
        """返回可用的 Tool 列表"""
        return {
            "tools": [
                {
                    "name": "query_database",
                    "description": (
                        "在 SQLite 数据库中执行 SELECT 查询,返回结果。"
                        "仅支持 SELECT 语句。结果以 JSON 数组形式返回,"
                        "每行是一个对象,键名为列名。最多返回 100 行。"
                    ),
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "sql": {
                                "type": "string",
                                "description": "要执行的 SQL SELECT 查询语句",
                            }
                        },
                        "required": ["sql"],
                    },
                },
                {
                    "name": "list_tables",
                    "description": "列出数据库中所有的表名",
                    "inputSchema": {
                        "type": "object",
                        "properties": {},
                    },
                },
            ]
        }

    def handle_tools_call(self, params: dict) -> dict:
        """执行 Tool 调用"""
        tool_name = params.get("name", "")
        arguments = params.get("arguments", {})

        if tool_name == "query_database":
            return self._execute_query(arguments.get("sql", ""))
        elif tool_name == "list_tables":
            return self._list_tables()
        else:
            raise ValueError(f"Unknown tool: {tool_name}")

    def handle_resources_list(self, params: dict) -> dict:
        """返回可用的 Resource 列表"""
        return {
            "resources": [
                {
                    "uri": "db://tables",
                    "name": "所有数据表",
                    "description": "数据库中所有表名列表",
                    "mimeType": "application/json",
                }
            ]
        }

    def handle_resources_read(self, params: dict) -> dict:
        """读取指定 Resource"""
        uri = params.get("uri", "")
        if uri == "db://tables":
            tables = self._get_all_tables()
            return {
                "contents": [
                    {
                        "uri": uri,
                        "mimeType": "application/json",
                        "text": json.dumps(tables, ensure_ascii=False, indent=2),
                    }
                ]
            }
        # 动态 URI 模板: db://tables/{table_name}/schema
        if uri.startswith("db://tables/") and uri.endswith("/schema"):
            table_name = uri[len("db://tables/"):-len("/schema")]
            schema = self._get_table_schema(table_name)
            return {
                "contents": [
                    {
                        "uri": uri,
                        "mimeType": "application/json",
                        "text": json.dumps(schema, ensure_ascii=False, indent=2),
                    }
                ]
            }
        raise ValueError(f"Unknown resource: {uri}")

    # --- 内部实现 ---

    def _execute_query(self, sql: str) -> dict:
        """安全执行 SELECT 查询"""
        sql_stripped = sql.strip().upper()
        if not sql_stripped.startswith("SELECT"):
            raise ValueError("仅支持 SELECT 查询语句")
        if any(
            keyword in sql_stripped
            for keyword in ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE"]
        ):
            raise ValueError("不允许执行写操作或 DDL 语句")

        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            cursor = conn.execute(sql)
            rows = [dict(row) for row in cursor.fetchmany(100)]
            return {
                "content": [
                    {
                        "type": "text",
                        "text": json.dumps(rows, ensure_ascii=False, default=str),
                    }
                ]
            }
        finally:
            conn.close()

    def _list_tables(self) -> dict:
        tables = self._get_all_tables()
        return {
            "content": [
                {
                    "type": "text",
                    "text": json.dumps(tables, ensure_ascii=False),
                }
            ]
        }

    def _get_all_tables(self) -> list[str]:
        conn = sqlite3.connect(self.db_path)
        try:
            cursor = conn.execute(
                "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
            )
            return [row[0] for row in cursor.fetchall()]
        finally:
            conn.close()

    def _get_table_schema(self, table_name: str) -> dict:
        conn = sqlite3.connect(self.db_path)
        try:
            cursor = conn.execute(f"PRAGMA table_info({table_name})")
            columns = [
                {
                    "name": row[1],
                    "type": row[2],
                    "nullable": not row[3],
                    "default": row[4],
                    "primary_key": bool(row[5]),
                }
                for row in cursor.fetchall()
            ]
            return {"table": table_name, "columns": columns}
        finally:
            conn.close()

    # --- JSON-RPC 辅助方法 ---

    def _response(self, req_id: int, result: dict) -> dict:
        return {"jsonrpc": "2.0", "id": req_id, "result": result}

    def _error(self, req_id: int | None, code: int, message: str) -> dict:
        return {
            "jsonrpc": "2.0",
            "id": req_id,
            "error": {"code": code, "message": message},
        }


def main():
    """主循环:从 stdin 读取 JSON-RPC 请求,写入 stdout"""
    server = SQLiteMCPServer(db_path="data.db")

    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue

        try:
            request = json.loads(line)
            response = server.handle_request(request)
            if response is not None:
                sys.stdout.write(json.dumps(response, ensure_ascii=False) + "\n")
                sys.stdout.flush()
        except json.JSONDecodeError:
            # 非 JSON 行,忽略
            pass


if __name__ == "__main__":
    main()

5.3 关键设计决策解读

这个 150 行的实现中,有几个设计决策值得单独拿出来聊:

安全优先的 SQL 白名单。 _execute_query 方法没有简单地把 SQL 语句透传给 SQLite 执行,而是先检查语句是否以 SELECT 开头,并拒绝包含 INSERT/UPDATE/DELETE/DROP/ALTER/CREATE 的语句。这层白名单校验在前,SQLite 的执行在后——MCP Server 是安全的第一道防线,不应该依赖调用方来保证安全。

内容格式使用标准 MIME type。 resources/read 的返回内容标注了 mimeType: "application/json",这让 Host 端能够正确解析和展示返回数据,而不是把 JSON 当纯文本显示给模型。

错误处理分层。 最外层 handle_request 捕获所有异常并转为 JSON-RPC 错误响应,确保 Server 不会因为某个工具的异常而崩溃连接。每个工具内部也可以抛出业务异常,统一由外层处理。

5.4 在 Claude Desktop 中配置

写完 Server 后,在 Claude Desktop 的配置文件中注册:

{
  "mcpServers": {
    "sqlite": {
      "command": "python",
      "args": ["/path/to/mcp-sqlite-server/server.py"]
    }
  }
}

配置完成后重启 Claude Desktop,模型就能自动发现并使用 query_databaselist_tables 两个工具。整个过程不需要写任何胶水代码——MCP Client 自动完成了工具发现、参数验证和结果解析。

06 总结与展望

核心要点回顾

  • MCP 解决了 “N×M 适配矩阵” 问题。 在 MCP 之前,每个 AI 应用都要为每个工具单独写适配代码。MCP 用 Client-Server + 标准协议把 N×M 变成 N+M,让工具实现一次即可随处使用。
  • 三层抽象各司其职。 Tools 负责"动手"(执行操作),Resources 负责"查阅"(获取数据),Prompts 负责"懂得怎么问"(预置交互模板)。三者共同构成了一个完整的"能力界面"。
  • stdio 优先是务实的工程选择。 对绝大多数本地工具场景,子进程通信比网络通信更简单、更快、更安全。HTTP 作为远程场景的补充,而非默认选项。
  • MCP、A2A、ACP 是互补关系而非竞争关系。 MCP 做纵向集成(应用到工具),A2A 做横向协作(Agent 到 Agent),ACP 做通信基础设施。它们解决的是不同层次的问题。
  • MCP Server 可以很简单。 150 行 Python 代码就能实现一个功能完整的 MCP Server。协议的复杂度集中在规范文档里,落到代码上并不重。

MCP 当前面临的挑战

MCP 生态虽然蓬勃,但远未成熟。几个当前的主要挑战:

安全性仍是薄弱环节。 stdio 传输模式下的子进程隔离看似安全,但 HOST 应用本身的权限过大——Claude Desktop 如果被授予文件系统 MCP Server 的全部权限,一次错误的工具调用就可能删除重要文件。更细粒度的权限控制(按工具、按资源 URI、按操作类型)是下一步必须完善的。

工具发现和去重。 当用户装了 10 个 MCP Server,每个 Server 都暴露了不同的 Tools 和 Resources,模型如何快速判断"该调用哪个"成为一个非平凡问题。当多个 Server 提供了相似的工具(如两个不同的 query_database),现有的基于文本描述的匹配机制还不够稳健。

生态碎片化的风险。 MCP 的开放性既是优势也是隐患。如果不同平台对 MCP 规范的实现有细微差异(如 JSON-RPC 的错误码约定、工具调用的超时行为),可能产生新的"方言"碎片——就像 SQL 标准一样,每个数据库都有自己的"扩展"。

你应该现在就开始用 MCP 吗?

答案是分情况的:

如果你的团队在构建 AI 应用的工具集成层——现在就值得投入。MCP 的标准化降低了客户切换工具的成本,也为你的工具打开了更多分发渠道。

如果你在评估技术选型——MCP 是最务实的选择。它已经有足够的社区基础和工具生态,协议设计经过了一年半的生产验证,在三个协议中成熟度最高。

如果你在观望——至少花一小时按照本文第 5 节的教程写一个 MCP Server。动手比看文档有用得多——你会发现很多"看起来复杂"的设计选择,写到代码里其实很自然。


延伸阅读:

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐