1. 概述
这是《ALL IN AI》专栏的第二篇文章。本篇的主角,是近年来 AI 领域备受关注的开放协议------MCP。
在上一篇文章《一文带你掌握 LLM、Token、Context、Prompt、RAG、MCP、Skill、Agent 等 AI 核心概念》中,我们已经对 MCP 做过简要介绍。本文将在此基础上进一步展开,系统讲清楚以下几个问题:
- 为什么 AI 应用需要 MCP?
- MCP 到底是什么,又不是什么?
- Host、Client、Server 分别承担什么职责?
- Tools、Resources、Prompts 有什么区别?
- 一次 MCP 工具调用是如何完成的?
- 如何开发并接入一个 MCP Server?
- 在实际项目中使用 MCP 时,需要遵循哪些最佳实践?
先来看一张 MCP 架构图,对它建立一个整体印象:

2. 为什么需要 MCP?
基础大模型擅长理解、推理和生成内容,但它本身不能主动访问实时数据,也不能直接操作文件、数据库或业务系统。一个模型是否具备联网搜索、文件读写或调用业务接口的能力,取决于承载模型的 AI 应用是否为它接入了相应工具。
例如,在 DeepSeek 中关闭"智能搜索"工具后询问:"明天杭州天气怎么样?",模型无法直接获得实时天气数据:

这说明基础模型与外部世界之间存在一道天然边界。要让模型真正进入业务工作流,AI 应用通常需要连接:
- 文件系统,用于读取和修改本地文件;
- 数据库,用于查询和更新业务数据;
- GitHub、GitLab,用于读取 Issue、PR 和代码;
- Slack、飞书、Notion、Google Drive,用于获取团队上下文;
- 浏览器、自动化脚本和内部 API,用于执行真实操作。
在没有统一协议的情况下,每个 AI 应用都需要针对每个外部系统编写一套适配代码:
erlang
每个 AI 应用 → 为每个外部系统编写定制集成
Claude ↔ Slack (一套定制代码)
Claude ↔ GitHub (一套定制代码)
Claude ↔ Jira (一套定制代码)
ChatGPT ↔ Slack (另一套定制代码)
ChatGPT ↔ GitHub (另一套定制代码)
...
如果有 M 个 AI 应用和 N 个外部系统,理论上最多可能产生 M × N 组集成关系。随着应用和工具数量增加,开发、维护、鉴权和升级成本都会迅速上升。
MCP 要解决的核心问题,就是让 AI 应用与外部系统之间形成标准化连接。
可以先用一句话理解 MCP:
MCP(Model Context Protocol,模型上下文协议)是一套开放协议,用于标准化 AI 应用连接外部工具、数据和提示模板的方式。
它并不替代大模型,也不负责模型推理,而是为 AI 应用接入外部能力提供统一的协议层。
3. MCP 到底是什么?
MCP 的全称是 Model Context Protocol,由 Anthropic 发起并开源,现已发展为面向 AI 应用生态的开放标准。
如果把 USB-C 理解为电子设备之间的通用连接标准,那么 MCP 可以理解为 AI 应用与外部能力之间的通用连接标准。只要客户端和服务端都遵循同一套协议,它们就可以通过统一的消息格式完成能力发现、调用和结果返回。
需要特别说明的是,MCP Server 不会直接被大模型调用。真正与模型交互的是 MCP Host,也就是承载 AI 能力的应用。MCP Server 通过 Host 和 Client,将外部数据与能力提供给模型使用。
因此,名称中的 Context 不应只理解为一段文本,而应理解为模型完成任务时可获得的广义上下文,包括:
- 可读取的数据;
- 可调用的工具;
- 可复用的提示模板;
- 工具执行后返回的结果。
3.1 MCP 不是什么?
为了避免概念混淆,还需要明确 MCP 的能力边界:
- MCP 不是大模型,也不负责模型训练或推理;
- MCP 不是 Agent 框架,不负责完整的任务规划和自主循环;
- MCP 不是某个工具的具体实现,只规定工具如何被描述、发现和调用;
- MCP 不是权限系统本身,具体的身份认证、授权和审批仍需由 Host、Server 及基础设施共同实现;
- MCP 不等同于 Function Calling,二者处于不同层次。
Function Calling 主要解决"模型如何用结构化格式表达一次工具调用";MCP 主要解决"AI 应用如何以统一协议发现并连接外部能力"。在实际系统中,Host 可以先让模型通过 Function Calling 生成工具调用,再通过 MCP 将请求路由到对应的 Server。
3.2 三类核心能力
MCP Server 可以向客户端暴露三类核心能力:
| 能力 | 主要控制方 | 作用 | 示例 |
|---|---|---|---|
| Tools | 模型 | 提供可执行函数,用于查询信息或执行操作 | 查询天气、搜索文档、创建工单、发送消息 |
| Resources | 应用 | 提供由客户端读取和组织的上下文数据 | 文件内容、数据库 Schema、用户资料、项目文档 |
| Prompts | 用户 | 提供可选择、可复用的提示模板 | 代码审查模板、故障排查模板、周报生成模板 |
三者的区别不在于"是否包含数据",而在于它们的交互方式和主要控制方。
Tools:可执行的能力
Tools 是 MCP Server 暴露的可调用函数。它既可以执行只读查询,也可以产生外部副作用:
scss
"出行服务 Server"的 Tools:
├── get_weather(city, date) → 查询目的地天气
├── search_flights(city, date) → 查询可选航班
├── book_flight(flight_no) → 预订机票
└── cancel_booking(order_id) → 取消订单
其中,get_weather 和 search_flights 是只读工具,book_flight 和 cancel_booking 则会改变外部系统状态。因此,不能简单地把 Tool 等同于"写操作"。
Resources:应用管理的上下文
Resources 用于向客户端提供可读取的数据:
arduino
"出行服务 Server"的 Resources:
├── policy://refund → 退改签规则
├── profile://traveler/current → 当前用户的出行偏好
└── itinerary://order/12345 → 指定订单的行程信息
Resources 通常由 Host 或应用逻辑决定何时读取、如何展示,以及是否加入模型上下文。它强调的是"上下文数据的标准化访问",而不是让模型直接操作外部系统。
Prompts:用户选择的提示模板
Prompts 是 Server 提供的可复用提示模板:
scss
"出行服务 Server"的 Prompts:
├── plan_trip(destination, days) → 生成旅行规划提示
└── handle_delay(order_id) → 生成航班延误处理提示
Prompt 可以包含参数和预设消息,但它本身不等同于自动执行多个工具的工作流。用户选择 Prompt 后,客户端会将生成的消息加入对话,后续是否调用工具仍由模型、Host 策略和用户授权共同决定。
可以用一句话概括:
Resources 提供上下文,Tools 提供可执行能力,Prompts 提供可复用的交互入口。
4. MCP 的基本架构
MCP 采用 Client-Server 架构,主要包含 Host、Client 和 Server 三类角色:
| 角色 | 是什么 | 主要职责 | 示例 |
|---|---|---|---|
| MCP Host | 承载 AI 能力的应用 | 管理会话、调用模型、聚合上下文、控制权限、协调工具调用 | Claude Code、Claude Desktop、VS Code、Cursor 等 |
| MCP Client | Host 内部的协议客户端 | 与某个 Server 建立会话、协商版本和能力、发送请求、接收响应与通知 | Claude Code 内部的 MCP 连接组件 |
| MCP Server | 对外暴露能力的程序或服务 | 提供 Tools、Resources、Prompts,可运行在本地或远端 | Weather MCP Server、GitHub MCP Server |
整体关系如下图所示:

一个 Host 可以连接多个 MCP Server。通常情况下,Host 会为每个 Server 创建一个独立的 Client,并保持一对一会话关系:
arduino
┌─ MCP Client A ↔ Filesystem MCP Server
用户 ↔ MCP Host ─────┼─ MCP Client B ↔ GitHub MCP Server
└─ MCP Client C ↔ Database MCP Server
这种设计有两个重要意义:
- 能力隔离:每个 Server 只暴露自己负责的数据和工具;
- 安全隔离:Server 之间不应直接看到彼此的数据,也不应默认获得完整对话内容。
Host 是整个架构中的协调者。它决定连接哪些 Server、向模型提供哪些能力、是否要求用户确认,以及将哪些结果重新放入模型上下文。
5. MCP 的协议分层与传输方式
MCP 可以从数据层和传输层两个层次理解。
5.1 数据层
数据层定义客户端与服务端交换消息时使用的结构和语义,主要包括:
- 基于 JSON-RPC 2.0 的请求、响应和通知;
- 生命周期管理;
- 协议版本与能力协商;
- Tools、Resources、Prompts 等服务端能力;
- 日志、进度通知等通用机制。
MCP 使用 JSON-RPC 2.0 作为基础消息格式。需要服务端返回结果的消息使用"请求---响应"模式;不需要返回结果的消息则可以使用通知。
例如,客户端初始化连接时会发送 initialize 请求:
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
这里的版本号表示客户端优先支持的协议版本。Server 会在响应中返回最终采用的版本;如果双方无法协商出兼容版本,则应终止连接。
5.2 传输层
传输层负责承载数据层消息,当前主要包括两种标准传输方式:
| 方式 | 说明 | 常见场景 |
|---|---|---|
| stdio | Client 启动 Server 子进程,并通过标准输入、标准输出交换消息 | 本地文件系统、开发工具、本地数据库 |
| Streamable HTTP | 通过 HTTP POST/GET 通信,可选用 SSE 承载流式消息 | SaaS 服务、云数据库、企业内部服务 |
需要注意,旧版的 HTTP+SSE 传输方式已被 Streamable HTTP 取代。SSE 仍然可以使用,但它是 Streamable HTTP 中可选的流式机制,而不是与 HTTP 并列的独立新方案。
本地和远程是常见的部署方式,stdio 与 Streamable HTTP 是协议定义的传输方式。二者相关,但不是完全等价的分类:
- 本地 Server 通常使用 stdio;
- 远程 Server 通常使用 Streamable HTTP;
- 实际实现仍应根据安全性、部署环境和客户端支持情况选择。
6. MCP Server 实例与应用
下面通过两个案例说明如何开发本地 MCP Server,以及如何连接远程 MCP Server。
6.1 构建并连接本地 MCP Server
本节以天气服务为例,使用 Python 和官方 MCP Python SDK 构建一个可运行的 MCP Server,再通过 Claude Code 进行连接。
环境准备
示例要求安装 Python 3.10 或更高版本。首先安装 uv:
arduino
curl -LsSf https://astral.sh/uv/install.sh | sh
安装后重新打开终端,然后创建项目:
bash
uv init weather
cd weather
uv venv
source .venv/bin/activate
uv add "mcp[cli]>=1.27,<2" httpx
touch weather.py
编写 Weather MCP Server
本文示例使用 MCP Python SDK 1.x 的稳定 API,因此显式添加了 <2 版本约束。SDK 2.x 对部分接口进行了重新设计,如果你使用的是 2.x,请参考对应版本的迁移文档,不要直接混用不同主版本的代码。
将以下代码写入 weather.py:
python
import json
import logging
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
NWS_API_BASE = "https://api.weather.gov"
WTTR_BASE = "https://wttr.in"
USER_AGENT = "weather-mcp/1.0"
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def get_json(
url: str,
*,
headers: dict[str, str] | None = None,
params: dict[str, str] | None = None,
) -> dict[str, Any] | None:
"""请求 JSON 数据,并对网络或解析异常进行统一处理。"""
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
except (httpx.HTTPError, json.JSONDecodeError) as exc:
logger.warning("Weather API request failed: %s", exc)
return None
def format_alert(feature: dict[str, Any]) -> str:
props = feature.get("properties", {})
return "\n".join(
[
f"Event: {props.get('event', 'Unknown')}",
f"Area: {props.get('areaDesc', 'Unknown')}",
f"Severity: {props.get('severity', 'Unknown')}",
f"Description: {props.get('description', 'No description')}",
f"Instructions: {props.get('instruction', 'No instructions')}",
]
)
@mcp.tool()
async def get_alerts(state: str) -> str:
"""查询美国指定州的有效天气预警。
Args:
state: 两位美国州代码,例如 CA、NY。
"""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json",
}
data = await get_json(
f"{NWS_API_BASE}/alerts/active/area/{state.upper()}",
headers=headers,
)
if not data or "features" not in data:
return "暂时无法获取天气预警,请稍后重试。"
features = data["features"]
if not features:
return f"{state.upper()} 当前没有有效天气预警。"
return "\n\n---\n\n".join(format_alert(item) for item in features[:10])
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""根据经纬度查询美国境内的天气预报。
Args:
latitude: 纬度。
longitude: 经度。
"""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json",
}
points = await get_json(
f"{NWS_API_BASE}/points/{latitude},{longitude}",
headers=headers,
)
if not points:
return "无法获取该位置的天气网格信息。"
forecast_url = points.get("properties", {}).get("forecast")
if not forecast_url:
return "天气服务未返回预报地址。"
forecast = await get_json(forecast_url, headers=headers)
if not forecast:
return "无法获取详细天气预报。"
periods = forecast.get("properties", {}).get("periods", [])[:5]
if not periods:
return "该位置暂无可用天气预报。"
results = []
for period in periods:
results.append(
"\n".join(
[
f"{period.get('name', 'Unknown')}:",
f"Temperature: {period.get('temperature')}°"
f"{period.get('temperatureUnit', '')}",
f"Wind: {period.get('windSpeed', 'Unknown')} "
f"{period.get('windDirection', '')}",
f"Forecast: {period.get('detailedForecast', 'Unknown')}",
]
)
)
return "\n\n---\n\n".join(results)
@mcp.tool()
async def get_global_forecast(city: str) -> str:
"""查询全球城市的当前天气和未来三天预报。
Args:
city: 城市名称,可附带国家或地区,例如 Shanghai、London,UK。
"""
data = await get_json(
f"{WTTR_BASE}/{city}",
params={"format": "j1"},
)
if not data:
return f"无法获取 {city} 的天气,请检查城市名称后重试。"
current_list = data.get("current_condition", [])
if not current_list:
return f"天气服务未返回 {city} 的实时天气。"
current = current_list[0]
description = current.get("weatherDesc", [{}])[0].get("value", "Unknown")
result = [
f"地点:{city}",
f"当前天气:{description}",
f"温度:{current.get('temp_C', 'N/A')}°C",
f"体感温度:{current.get('FeelsLikeC', 'N/A')}°C",
f"湿度:{current.get('humidity', 'N/A')}%",
"",
"未来天气:",
]
for day in data.get("weather", [])[:3]:
result.append(
f"- {day.get('date', 'Unknown')}:"
f"{day.get('mintempC', 'N/A')}~{day.get('maxtempC', 'N/A')}°C"
)
return "\n".join(result)
if __name__ == "__main__":
mcp.run(transport="stdio")
这段代码通过 @mcp.tool() 将三个 Python 函数声明为 MCP Tools:
get_alerts:查询美国指定州的天气预警;get_forecast:通过经纬度查询美国天气;get_global_forecast:通过城市名称查询全球天气。
函数签名和 Docstring 非常重要。SDK 会据此生成工具名称、描述和输入参数 Schema,而这些信息会直接影响模型能否正确选择工具、填写参数。
连接 Server
在需要使用该 Server 的项目根目录创建 .mcp.json:
bash
{
"mcpServers": {
"weather": {
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"${WEATHER_MCP_DIR}",
"run",
"weather.py"
]
}
}
}
先将 WEATHER_MCP_DIR 设置为 Weather 项目的实际路径,再从目标项目目录启动 Claude Code。相比在共享配置中写死个人绝对路径,环境变量更适合跨机器使用。
常用配置字段如下:
| 字段 | 必需 | 说明 | 示例 |
|---|---|---|---|
type |
否 | Server 类型,stdio 配置中可显式声明 | "stdio" |
command |
是 | 启动命令 | "uv"、"npx"、"python" |
args |
否 | 传给启动命令的参数数组 | ["run", "weather.py"] |
env |
否 | 传给 Server 进程的环境变量 | {"API_KEY": "${API_KEY}"} |
Claude Code 的启动超时可以通过 MCP_TIMEOUT 环境变量配置。例如,MCP_TIMEOUT=10000 claude 表示将 MCP Server 启动超时设置为 10 秒。具体配置能力可能随客户端版本变化,应以当前客户端文档为准。
进入 Claude Code 后执行 /mcp,即可查看 Server 的连接状态:

调用工具
连接成功后,可以直接询问:
明天杭州的天气如何?
Claude Code 会根据工具描述选择 get_global_forecast,填入城市参数,并通过 MCP 调用 Weather Server:

实际项目中,不一定需要从零开发 MCP Server。可以优先查找官方或可信厂商提供的实现,再根据安全性、维护状态、权限范围和部署方式进行评估。对于第三方 MCP Server,不应仅因为"安装方便"就默认信任。
6.2 连接远程 MCP Server
下面以 GitHub Remote MCP Server 为例,通过 Claude Code 添加远程 HTTP Server:
csharp
claude mcp add --transport http github https://api.githubcopilot.com/mcp/
添加后,在 Claude Code 中执行 /mcp,按照界面提示完成 OAuth 认证。相比把长期有效的 Personal Access Token 直接写入命令或配置文件,OAuth 更适合交互式使用场景。
如果确实需要使用 Token,应通过环境变量或安全凭据存储注入,并遵循最小权限原则,避免将凭据写入项目文件、命令历史或日志。
常用管理命令如下:
csharp
# 添加远程 MCP Server
claude mcp add --transport http <服务器名> <服务器地址>
# 列出所有 MCP Server
claude mcp list
# 查看指定 Server 的详情
claude mcp get github
# 删除指定 Server
claude mcp remove github
连接并完成认证后,即可让 Claude Code 查询仓库、Issue 或 Pull Request。涉及创建、修改或删除数据的操作时,仍应检查工具参数并保留必要的人工确认。

6.3 配置范围
Claude Code 支持三种 MCP Server 配置范围:
| 范围 | 可见性 | 适用场景 | 配置示例 |
|---|---|---|---|
| local(默认) | 仅当前用户、当前项目可用 | 私人配置、实验性 Server、敏感配置 | claude mcp add --scope local ... |
| project | 项目成员共享,保存在 .mcp.json |
团队统一使用的项目工具 | claude mcp add --scope project ... |
| user | 当前用户的所有项目可用 | 跨项目使用的个人工具 | claude mcp add --scope user ... |
同名 Server 的优先级为:
sql
local > project > user
需要注意:
project范围的.mcp.json可以提交到版本库,但其中不应包含真实密钥;- 机器相关路径应通过环境变量提供;
- 团队成员首次使用项目级 Server 时,应核对即将执行的命令或连接的地址;
- 不要因为配置来自代码仓库,就默认其中的 Server 是可信的。
7. MCP 的工作原理
下面以一次工具调用为例,看看 MCP 如何完成从连接建立到结果回填的完整闭环:

7.1 初始化:建立连接并协商能力
当 Host 连接 MCP Server 时,会为其创建 MCP Client,并发送 initialize 请求。
初始化阶段主要完成:
- 协商协议版本;
- 交换客户端和服务端信息;
- 声明双方支持的能力;
- 建立后续通信所需的会话状态。
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "all-in-ai-client",
"version": "1.0.0"
}
}
}
Server 会返回最终采用的协议版本、服务端信息和能力声明,例如是否支持 Tools、Resources、Prompts,以及是否支持能力列表变化通知。
完成初始化后,客户端还会发送 notifications/initialized,表示初始化阶段已经结束。
7.2 能力发现:获取可用工具
连接建立后,Host 并不知道 Server 提供了哪些工具,需要通过 Client 请求工具列表:
json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
Server 返回的每个工具通常包含:
name:工具名称;title:供界面展示的名称,可选;description:工具用途说明;inputSchema:输入参数的 JSON Schema;outputSchema:结构化输出的 JSON Schema,可选;annotations:只读、破坏性、幂等等行为提示,可选。
以天气工具为例,精简后的定义如下:
json
{
"name": "get_global_forecast",
"description": "查询全球城市的当前天气和未来三天预报。",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,可附带国家或地区。"
}
},
"required": ["city"]
}
}
工具定义不仅是接口文档,也是模型选择工具时的重要依据。含糊的名称、描述和参数设计,会直接降低调用准确率。
7.3 工具选择与请求路由
Host 会以模型所支持的方式,将可用工具提供给模型。当用户提出"明天杭州天气怎么样"时,模型判断需要实时天气数据,于是生成对 get_global_forecast 的工具调用。
需要注意,模型生成的工具调用并不会直接发送给 MCP Server。Host 会先检查工具是否可用、是否满足权限策略、是否需要用户确认,然后将请求路由到对应的 MCP Client。
Client 再向 Server 发送 tools/call:
json
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "get_global_forecast",
"arguments": {
"city": "Hangzhou"
}
}
}
Server 执行真实逻辑,并返回工具结果:
swift
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [
{
"type": "text",
"text": "地点:Hangzhou\n当前天气:Partly cloudy\n温度:34°C\n未来三天:..."
}
],
"isError": false
}
}
7.4 结果回填:重新进入模型上下文
工具返回结果后,Host 会将结果重新加入模型上下文,让模型基于真实数据继续推理并生成最终回答。
完整流程如下:
arduino
用户提出问题
↓
Host 向模型提供可用工具
↓
模型生成工具调用
↓
Host 检查权限并进行路由
↓
MCP Client 向 MCP Server 发送 tools/call
↓
MCP Server 执行逻辑并返回结果
↓
Host 将结果放回模型上下文
↓
模型生成最终回答,或继续发起下一次调用
这也是 MCP 经常与 Agent 一同出现的原因:Agent 或 Host 负责规划、决策和多步推进,MCP 负责将外部能力以标准化方式接入系统。
下面是用户询问"明天杭州天气怎么样"时的完整调用流程:

8. MCP 开发与使用的最佳实践
MCP 降低了系统集成成本,但"能够连接"不代表"可以不受约束地调用"。在真实项目中,还需要关注工具设计、安全、性能和可维护性。
8.1 设计清晰的工具接口
工具名称、描述和参数 Schema 会直接影响模型的选择结果:
- 使用明确、稳定的动词命名,例如
get_order、create_issue; - 在描述中说明工具用途、适用条件和限制;
- 为参数提供清晰的类型、枚举、格式与说明;
- 避免让一个工具同时承担多个无关职责;
- 对返回结果定义稳定的结构,必要时提供
outputSchema。
例如,与其设计一个含义模糊的 handle_order(action, data),不如拆分为:
scss
get_order(order_id)
cancel_order(order_id, reason)
update_shipping_address(order_id, address)
8.2 区分只读操作与写操作
查询、创建、修改和删除操作应尽量拆分为不同工具。这样可以:
- 让模型更准确地选择能力;
- 让 Host 实施不同的审批策略;
- 降低误调用和越权风险;
- 提高日志与审计的可读性。
Server 可以通过 Tool Annotations 提供 readOnlyHint、destructiveHint、idempotentHint 等行为提示,但这些字段只是提示,不是安全边界。客户端不能仅凭未经信任的 Server 声明就自动放行高风险操作。
8.3 对高风险操作保留人工确认
以下操作通常不应在缺少确认的情况下自动执行:
- 删除文件、数据或云资源;
- 发送邮件、消息或公开内容;
- 创建订单、付款或退款;
- 修改生产环境配置;
- 合并代码、发布版本或关闭工单;
- 批量修改外部系统状态。
确认界面应展示工具名称、关键参数、目标对象和可能产生的影响,而不是只显示一个笼统的"是否允许"。
8.4 遵循最小权限原则
无论本地还是远程 Server,都只应获得完成任务所必需的权限:
- 为不同工具拆分细粒度权限;
- 默认授予只读权限,按需提升;
- 缩小文件系统可访问目录;
- 限制数据库账号可访问的库、表和操作;
- 为 Token 设置最小 Scope 和合理有效期;
- 不要让所有 Server 共享同一组高权限凭据。
MCP 会让外部能力更容易组合,因此更需要关注多工具组合后的整体风险。
8.5 安全管理密钥和身份凭据
- 不要在
.mcp.json、源代码或命令参数中硬编码密钥; - 使用环境变量、系统钥匙串或 Secret Manager;
- 不要在日志中输出 Token、Cookie、Authorization Header;
- 远程生产服务应使用 HTTPS;
- 优先使用标准 OAuth 流程;
- 服务端必须验证 Token 的签发方、受众、有效期和权限;
- 不要将客户端传入的 Token 未经验证地透传给下游服务。
8.6 谨慎使用第三方 MCP Server
本地 MCP Server 本质上是以当前用户权限运行的程序,可能读取文件、访问网络或执行命令。安装前应检查:
- 发布者是否可信;
- 源代码和依赖是否可审计;
- 启动命令是否包含下载、提权或可疑脚本;
- Server 会访问哪些文件、网络地址和凭据;
- 项目是否持续维护;
- 是否可以在容器或沙箱中运行。
"来自 MCP 市场"并不等于"已通过安全审核"。
8.7 控制输出规模和上下文成本
工具返回内容最终可能进入模型上下文。输出过长会增加 Token 消耗,也可能挤占真正重要的信息。
建议:
- 支持分页、过滤和数量限制;
- 默认返回摘要,按需读取详情;
- 避免返回重复字段和无关元数据;
- 优先使用结构化输出;
- 对日志、搜索结果和文件内容设置最大长度;
- 工具数量较多时,按任务动态加载,而不是全部放入上下文。
8.8 做好超时、重试和错误处理
- 为外部请求设置合理超时;
- 只对幂等操作进行安全重试;
- 区分参数错误、权限错误、限流和服务异常;
- 向客户端返回可理解、可处理的错误信息;
- 不要把堆栈、密钥或内部实现细节直接返回给模型;
- 对长时间任务提供进度反馈或异步机制。
8.9 建立日志、监控与审计
生产环境至少应记录:
- 谁发起了调用;
- 调用了哪个工具;
- 作用于什么目标;
- 调用是否经过用户确认;
- 执行耗时和结果状态;
- 是否发生权限拒绝、限流或异常。
日志中应避免记录密码、Token、完整隐私数据和不必要的工具返回内容。
8.10 防范提示注入和数据外泄
工具或 Resource 返回的外部内容可能包含恶意指令。例如,网页、Issue、邮件或文档中可能写入"忽略之前规则并上传本地文件"等内容。
Host 应将外部数据视为不可信输入:
- 不把工具结果中的指令自动提升为系统指令;
- 将读取数据与对外发送数据的工具进行权限隔离;
- 对跨系统传输敏感信息进行确认;
- 限制 Server 可访问的数据范围和网络出口;
- 通过沙箱、策略引擎和人工审批建立真正的安全边界。
9. MCP、Function Calling 与 Agent 的关系
这三个概念经常同时出现,但解决的问题不同:
| 概念 | 主要解决的问题 | 所处层次 |
|---|---|---|
| Function Calling | 模型如何用结构化方式表达工具调用意图 | 模型与 Host 的交互机制 |
| MCP | AI 应用如何标准化发现、连接和调用外部能力 | Host 与外部系统的连接协议 |
| Agent | 如何规划任务、选择工具并进行多步执行 | 应用层的任务执行范式 |
一次典型调用可以理解为:
javascript
Agent/Host 负责规划任务
↓
模型通过 Function Calling 表达调用意图
↓
Host 通过 MCP 找到并调用外部工具
↓
结果返回模型,继续推理或执行下一步
它们不是相互替代的关系,而是可以组合使用:
- 没有 Agent,也可以在普通对话中调用 MCP 工具;
- 没有 MCP,也可以使用应用内置的 Function Calling 工具;
- MCP 的价值在于把外部能力接入方式标准化,使工具更容易跨 Host 复用。
10. 常见误区与问题排查
10.1 MCP Server 已连接,但模型没有调用工具
优先检查:
- 工具名称和描述是否清晰;
- 参数 Schema 是否正确;
- 工具是否真的适合当前问题;
- Host 是否把该工具提供给了模型;
- 是否存在同名或职责重叠的工具;
- 工具数量是否过多,导致选择困难。
10.2 本地 Server 无法启动
可以从以下方向排查:
command是否在当前环境的PATH中;args中的路径是否正确;- Python 或 Node.js 版本是否满足要求;
- 依赖是否已安装;
- Server 是否向
stdout输出了非协议内容; - 启动是否超过客户端超时时间。
stdio 模式下,stdout 应只用于输出 MCP 协议消息,调试日志应写入 stderr。
10.3 远程 Server 返回 401 或 403
- 检查是否完成 OAuth 认证;
- 检查 Token 是否过期;
- 检查 Scope 是否包含目标工具所需权限;
- 检查 Server 是否验证了正确的 Token Audience;
- 检查组织策略是否禁止连接该 Server。
10.4 工具调用成功,但回答质量不理想
- 减少无关输出;
- 返回结构化数据;
- 明确字段含义和单位;
- 为错误结果设置
isError; - 在工具描述中写清适用条件;
- 让 Host 只把真正需要的信息放回上下文。
11. 总结
MCP 的核心价值,不是让模型突然拥有新的推理能力,而是为 AI 应用连接外部世界提供一套统一协议。
通过 MCP:
- Host 可以统一管理外部能力和用户会话;
- Client 可以按照标准协议与 Server 通信;
- Server 可以用一致的方式暴露 Tools、Resources 和 Prompts;
- 开发者可以减少重复集成,提升工具的可复用性;
- 团队可以在统一接口之上继续建设权限、审批、监控和审计能力。
理解 MCP 时,可以抓住三条主线:
- 架构上:Host 负责协调,Client 负责通信,Server 负责提供能力;
- 协议上:数据层定义消息语义,传输层负责承载消息;
- 应用上:模型或 Agent 负责决策,MCP 负责标准化连接,安全策略负责约束执行。
MCP 降低了 AI 应用连接工具和数据的门槛,但它不会自动解决权限、安全和可靠性问题。真正可用于生产环境的 MCP 系统,还需要清晰的工具设计、最小权限、人工确认、凭据保护、输出控制,以及完整的监控审计机制。
当越来越多的 AI 应用和外部服务采用统一协议后,开发者就不必反复为每一种组合编写定制连接代码。这正是 MCP 最值得关注的地方:它正在尝试为 AI 应用建立一套通用、可组合、可治理的能力连接层。