第 15 章 MCP 服务器:让 Claude 直连外部工具与数据源
第 14 章的 Hooks 解决"在什么时机自动做什么",本章解决"Claude 手里没有这把工具怎么办"。MCP 是 Claude Code 能力扩展的主通道:数据库、GitHub、文件系统、浏览器、搜索引擎,凡是你能写成服务器的工具,都能挂进 Claude 的工具箱。第 16 章的 Skills 给的是工作流指令,而 MCP 给的是真实可调用的工具——两者分工不同,后面会对照讲。
15.1 结论:MCP 是给 Claude Code 接外部工具的标准协议
MCP(Model Context Protocol)是 Anthropic 于 2024-11 发布的开放标准,作用一句话:让 Claude 直连数据库/GitHub/文件系统,不用你手动中转。
没有 MCP 时,你想让 Claude 查 PostgreSQL,得自己跑 psql -c "\d orders"、复制结果、粘贴回对话;查 GitHub Issues 得 gh issue list、再贴回去。这条"人肉中转"链路有两个问题:一是信息密度被你的复制粒度截断,Claude 看不到原始结构;二是注意力被低价值操作占用,你成了管道工。
MCP 把这条链路压成一步:Claude 直接调用 mcp__postgres__describe_table("orders"),拿到原始 schema,再决定下一步。你的角色从"管道工"回到"架构师与审查者"——这与第 06 章的核心心法完全一致。
15.2 MCP 是什么:协议定位与与直接 Bash 的区别
MCP 不是 Anthropic 私有 API,而是一份开放协议,任何 server 实现都可被任何 MCP 客户端(Claude Code、Claude Desktop、VS Code、Zed 等)消费。它的定位介于两层之间:
- 下层:外部系统——PostgreSQL、GitHub API、本地文件系统、Selenium 浏览器、Jira、Notion
- 上层:Claude 的工具调用——Claude 决定调用哪个工具、传什么参数,MCP 负责把调用路由到下层系统并把结果结构化返回
你可能会问:Claude Code 已经能跑 Bash,psql gh curl 都能直接用,为什么还要 MCP?这正是新人最常踩的认知坑。两者区别如下:
| 维度 | 直接 Bash | MCP 服务器 |
|---|---|---|
| 结构化 | Claude 生成命令字符串,输出是自由文本需要解析 | 工具有显式 schema(参数名、类型、描述),输出结构化 |
| 权限隔离 | Bash 权限是"允许跑这条命令",粒度粗,且一旦放开 psql 就等于放开整个数据库 | MCP 权限按工具粒度,query 只读、execute 才写,且 server 内部可做 ACL |
| 可复用 | 每个项目重写一遍 psql -h ... -U ... 命令模板 | 一次配置,全团队 clone .mcp.json 即用 |
| 跨客户端 | Bash 命令绑死在 Claude Code 里 | 同一个 MCP server 可被 Claude Desktop、VS Code、Zed 共用 |
| 错误处理 | 命令失败靠 Claude 读 stderr 猜 | server 返回结构化 error code,Claude 能精确决策 |
| 凭据管理 | 密码散落在命令历史、env 导出、shell 脚本 | 凭据集中在 .mcp.json 的 env 或 OAuth,不进 shell 历史 |
一句话:Bash 是"Claude 替你敲命令",MCP 是"给 Claude 一把正经工具"。能写成 MCP 的重复性工具调用,就不该让 Claude 每次重新生成命令字符串。
15.3 四种传输方式对比
MCP server 与客户端之间有四种传输方式,选择取决于 server 跑在哪、是否需要双向通信、是否需要长连接:
| 传输 | 通道方向 | 适合位置 | 典型场景 | 选用优先级 |
|---|---|---|---|---|
| stdio | 双向(标准输入/输出) | 本地子进程 | 文件系统、本地数据库、浏览器自动化 | 本地工具首选 |
| http | 请求-响应 + 流式升级 | 远程服务 | 托管 server、企业内部 API | 远程首选(2025 起官方推荐 streamable HTTP) |
| sse | 服务端单向推送 + HTTP 上行 | 远程服务(旧) | 早期远程 server、老版本兼容 | 新部署不再用,仅兼容遗留 |
| ws | 全双工 WebSocket | 远程服务、低延迟 | 实时双向、订阅推送 | 需要服务端主动推送时才用 |
15.3.1 stdio:本地工具的默认选择
stdio 传输下,Claude Code 把 server 当子进程启动,通过标准输入输出收发 JSON-RPC 消息。@modelcontextprotocol/server-postgres、@playwright/mcp 都走这条路。
好处:无需开端口,凭据留在本机进程环境,延迟最低。坏处:每个 Claude Code 会话都会起一个子进程,多会话并发要留意资源占用。
15.3.2 http:远程工具的主流选择
http 传输下,Claude Code 通过 HTTP 请求与远程 server 通信,2025 年起 MCP 规范推荐 streamable HTTP(单端点支持 POST 上行 + SSE 流式下行),取代旧的 HTTP+SSE 双端点模式。托管服务(Sentry、Linear、Notion、GitHub Copilot MCP)普遍走这条路。
15.3.3 sse 与 ws:仅在有明确需求时用
sse(Server-Sent Events)是 MCP 早期规范定义的远程传输,用 GET 建立 SSE 流接收服务端推送、POST 上行客户端请求。新部署建议直接用 streamable HTTP,sse 仅在对接遗留 server 时出现。
ws(WebSocket)提供全双工通道,只有在 server 需要主动向 Claude 推送消息(如实时监控告警、长任务进度)时才有意义。绝大多数工具用不上。
决策树:本地工具 → stdio;远程工具 → http;遗留系统 → sse;需要服务端推送 → ws。九成场景落在 stdio 和 http 两种上。
15.4 配置:.mcp.json 与工具命名规则
MCP server 有三种安装范围(官方称 scope),与第 05 章权限层级同理:
| Scope | 配置文件 | 可见范围 |
|---|---|---|
local(默认) | ~/.claude.json 中本项目条目下 | 仅你、仅本项目 |
project | 项目根 .mcp.json | 所有 clone 该仓库的人 |
user | ~/.claude.json 顶层 mcpServers | 仅你、所有项目 |
团队共享走 project,把 .mcp.json 提交进 Git。claude mcp add 命令会按 --scope 参数写入对应文件,也可以直接手写 .mcp.json。
15.4.1 完整 .mcp.json 示例
下面这份 .mcp.json 同时配置了 PostgreSQL(stdio + 连接串作参数)与 GitHub(stdio + env 凭据),覆盖两种最常见的配置形态:
{
"mcpServers": {
"postgres": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://readonly_user:pass@localhost:5432/shop"
]
},
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxx"
}
}
}
}字段含义:
type—— 传输方式,取值stdio/http/sse/wscommandargs—— stdio 专用,Claude Code 启动子进程的命令与参数url—— http/sse/ws 专用,远程端点headers—— http/sse/ws 专用,自定义头(如Authorization: Bearer <token>)env—— stdio 专用,注入子进程的环境变量
等效 CLI 命令(任选其一):
# 用 CLI 写入 project scope
claude mcp add --scope project --transport stdio postgres -- \
npx -y @modelcontextprotocol/server-postgres \
"postgresql://readonly_user:pass@localhost:5432/shop"
claude mcp add --scope project --transport stdio github -- \
npx -y @modelcontextprotocol/server-github \
-e GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxxxxxxxxxxx15.4.2 工具命名规则:mcp__<server>__<tool>
Claude Code 内部把 MCP 工具按固定格式命名:
mcp__<server-name>__<tool-name>例如配置了名为 postgres 的 server,它暴露的 query 工具,完整名是 mcp__postgres__query。在权限规则、Hook 匹配、调试日志里看到的都是这个全名。配置 server 时起的名字会直接进入工具命名空间,因此:
- 不要用冲突名字——两个 server 都叫
github会互相覆盖,后加载的赢 - 不要与内置工具重名——
mcp__bash__run这种命名虽然技术上可行,但会让权限规则混乱 - 用语义化短名——
postgresgithubfilesystem即可,不需要mcp-postgres-prod
15.5 常用 MCP 服务器速查
下表是经过社区验证、可直接 npx 启动的常用 server,核对日期 2026-07-08:
| Server 名 | 功能 | 关键配置 | 适用场景 |
|---|---|---|---|
@modelcontextprotocol/server-postgres | 查询 PostgreSQL(只读 query、schema 描述) | 连接串作 args | 让 Claude 探索数据库结构、生成报表 SQL |
@modelcontextprotocol/server-github | Issues / PR / 仓库文件读写 | GITHUB_PERSONAL_ACCESS_TOKEN env | 让 Claude 读 Issue、改 PR 描述、查 CI 日志 |
@modelcontextprotocol/server-filesystem | 受限目录的文件读写 | 允许目录列表作 args | 给 Claude 沙箱化文件访问,比 Bash 更可控 |
@modelcontextprotocol/server-brave-search | Brave 搜索引擎 | BRAVE_API_KEY env | 联网检索,比 WebSearch 配额独立 |
@playwright/mcp | 浏览器自动化(导航、点击、截图) | command: npx,无需 token | E2E 测试、抓取动态页面、视觉验证 |
@modelcontextprotocol/server-puppet | Puppeteer 浏览器 | 类似 Playwright | 老项目兼容 |
@modelcontextprotocol/server-fetch | 抓取 URL 并转 Markdown | 无需凭据 | 替代 WebFetch,输出更可控 |
@modelcontextprotocol/server-memory | 知识图谱式持久记忆 | 本地 JSON 文件 | 跨会话长记忆,补 CLAUDE.md 不足 |
@modelcontextprotocol/server-sequential-thinking | 结构化分步推理 | 无需凭据 | 复杂问题拆解,效果见仁见智 |
完整目录见 Anthropic MCP Directory 与 MCP Servers 仓库。
配置完成后,Claude Code 在会话启动时自动发现已连接 server 暴露的工具,无需手动声明。Claude 会根据任务自主决定何时调用——你只需要用自然语言描述目标。
15.6 实战:配置 postgres MCP 后用自然语言查表
目标:让 Claude 直连本地 PostgreSQL shop 库,用自然语言查看 orders 表结构。
15.6.1 步骤一:写配置并验证连接
在项目根创建 .mcp.json(内容见 15.4.1 的 postgres 部分),然后启动新会话。首次看到 project-scope server,Claude Code 会弹出批准提示——这是防止克隆恶意仓库时自动启动子进程的安全机制,必须人工确认。
# 列出已配置 server(在 shell 中)
claude mcp list预期输出:
postgres: connected (stdio) — tools: query, list_tables, describe_table会话内用内置命令 /mcp 查看实时状态(/mcp 是真正内置命令,见第 06 章命令真实性总表):
/mcp预期输出:
MCP Servers:
● postgres (stdio) — connected
tools: query, list_tables, describe_table验证标准:状态显示 connected 且列出工具名。若显示 failed 或 disconnected,跳到 15.9 失败边界。
15.6.2 步骤二:自然语言提问
在会话里直接说:
查看 orders 表结构,并告诉我有哪些外键关联Claude 的实际执行路径(你能在工具调用日志里看到):
→ mcp__postgres__describe_table(table="orders")
← { columns: [
{name:"id",type:"integer",nullable:false},
{name:"user_id",type:"integer",nullable:false},
{name:"amount",type:"numeric(10,2)",nullable:false},
{name:"created_at",type:"timestamp",default:"now()"}
],
foreign_keys: [
{column:"user_id",references:"users(id)"}
]
}Claude 拿到结构化结果后,用自然语言回答你:
orders 表有 4 列:id(主键)、user_id(外键 → users.id)、amount(金额,精度 10/2)、
created_at(默认当前时间)。外键只有一个:user_id 关联 users 表的 id。15.6.3 验证与失败边界
验证:回答中列出的列名、类型、外键,与 psql -d shop -c "\d orders" 输出一致。你可以让 Claude 顺手跑 mcp__postgres__query("SELECT COUNT(*) FROM orders") 交叉验证连通性。
失败边界(逐项排查):
| 症状 | 根因 | 处理 |
|---|---|---|
ECONNREFUSED 127.0.0.1:5432 | Postgres 没启动或端口不对 | pg_isready 检查服务,核对连接串端口 |
password authentication failed for user "readonly_user" | 连接串里用户名/密码错 | 用 psql 手工连一次验证凭据,再回填 .mcp.json |
permission denied for table orders | 数据库用户没有该表权限 | GRANT SELECT ON orders TO readonly_user;,MCP 用户建议只读 |
npx: command not found | 本机没装 Node.js 18+ | 装 Node LTS,npx 随附 |
/mcp 显示 failed 但无详细错误 | 子进程启动即崩溃 | claude mcp get postgres 看配置;手动跑 npx -y @modelcontextprotocol/server-postgres "连接串" 看报错 |
| 工具能列出来但调用超时 | 防火墙、SSL 证书、DB 连接池耗尽 | 查 Postgres 日志 pg_stat_activity,看连接数 |
安全提醒:连接串里的密码会明文写进 .mcp.json。如果用 project scope 提交 Git,等于把数据库密码推到仓库。15.9 会专门讲这条红线。
15.7 自建 MCP:何时该自建,骨架长什么样
当现有 server 目录满足不了需求,且这个工具会被反复使用(团队内、跨项目),就该考虑自建 MCP server。典型信号:
- 同一套内部 API,你每周都要让 Claude 查几次(如公司内部的工单系统、监控平台)
- 某个 Bash 操作复杂到每次都要写 20 行脚本,且参数化后能复用
- 需要给 Claude 一个比 Bash 更细粒度的权限边界(只读某几个接口)
自建最小 MCP server 的概念骨架(Python,完整实现见第 21 章 MCP SDK):
from mcp.server import Server
from mcp.types import Tool, TextContent
server = Server("my-internal-api")
@server.list_tools()
async def list_tools():
return [Tool(
name="search_tickets",
description="搜索内部工单系统",
inputSchema={
"type": "object",
"properties": {
"keyword": {"type": "string"}
},
"required": ["keyword"]
}
)]
@server.call_tool()
async def call_tool(name, arguments):
if name == "search_tickets":
# 调用内部 API,返回结构化结果
results = await fetch_internal_tickets(arguments["keyword"])
return [TextContent(type="text", text=json.dumps(results))]
if __name__ == "__main__":
import asyncio
from mcp.server.stdio import stdio_server
asyncio.run(stdio_server(server))关键纪律:
- 工具要小而专——一个 server 暴露 3-5 个工具,不要堆 50 个,Claude 在工具列表过长时选择准确率下降
description是最重要字段——Claude 靠它判断何时调用,写清楚"做什么、不做什么、参数含义"inputSchema要严格——必填字段、类型、枚举值都写全,Claude 会按 schema 生成参数- 只读与写操作分 server——
my-api-readonly和my-api-write分开,权限规则好写
完整 SDK 详解、TypeScript/Python 双语言实现、测试与发布流程,在第 21 章展开。
15.8 MCP vs Skills vs Hooks:三者分工
这是全书最容易混淆的三件事。初学者常问:"我想让 Claude 自动查数据库,该用 MCP 还是 Skill 还是 Hook?"三者解决的是不同层面的问题:
| 维度 | MCP 服务器 | Skills(第 16 章) | Hooks(第 14 章) |
|---|---|---|---|
| 本质 | 工具协议,给 Claude 新工具 | 能力包,给 Claude 工作流指令 | 事件钩子,在动作前后自动触发 |
| 解决的问题 | Claude 手里没这把工具 | Claude 不知道按什么流程做 | 想在某时机自动执行,不等 Claude 决定 |
| 触发方 | Claude 自主决定调用 | 用户输入 /skill-name 或 Claude 主动用 | 系统事件(PreToolUse、PostToolUse 等)自动触发 |
| 配置位置 | .mcp.json 或 ~/.claude.json | ~/.claude/skills/ 或项目 .claude/skills/ | settings.json 的 hooks 字段 |
| 执行体 | 外部 server 进程 | Prompt + 脚本 | Shell 命令或脚本 |
| 典型例子 | mcp__postgres__query | /deep-research /code-review | 提交前自动跑 npm test |
| 能力边界 | 能调用任何外部系统 | 能编排多步任务,可调用 MCP 工具 | 不进入 Claude 推理,纯外部自动化 |
决策树:
- Claude 缺工具(查 DB、调 API、操作浏览器)→ MCP
- Claude 有工具但不知道流程怎么做(代码审查流程、研究流程)→ Skill
- 想在某个时机强制执行,不让 Claude 决定(提交前必跑测试)→ Hook
三者经常组合用:Hook 在 PostToolUse 触发 /code-review Skill,Skill 内部调用 mcp__github__create_issue 工具。这是 Claude Code 扩展能力的完整三层栈。
15.9 失败边界:三个最常踩的坑
15.9.1 MCP 服务器崩溃:子进程退出与远程超时
stdio server 崩溃:Claude Code 启动子进程后,若 server 因配置错误、依赖缺失、未捕获异常退出,/mcp 会显示 failed 或 disconnected。排查步骤:
# 1. 看 Claude Code 的 MCP 日志
cat ~/.claude/logs/mcp-server-postgres.log
# 2. 手动跑 server,直接看 stderr
npx -y @modelcontextprotocol/server-postgres \
"postgresql://readonly_user:pass@localhost:5432/shop" 2>&1
# 3. 验证 npx 能拉到包
npx -y @modelcontextprotocol/server-postgres --versionhttp/sse/ws server 超时:远程 server 不在你控制下,常见症状是工具调用挂起 30 秒后超时。排查:
curl -v <url>验证端点可达- 检查
headers里 token 是否过期(OAuth token 一般 1 小时失效,需重新/mcp认证) - 公司网络代理是否拦截了出站 HTTPS
缓解策略:在 settings.json 配置 MCP 超时与重试(详见第 99 附录),关键任务不要单点依赖远程 server。
15.9.2 工具命名冲突:同名 server 互相覆盖
.mcp.json 里两个 server 同名,JSON 解析阶段就会被后写的覆盖,且无警告。更隐蔽的是:项目 .mcp.json 定义了 github,用户 ~/.claude.json 也定义了 github,两个 scope 同时存在时优先级是 project > user > local(project 覆盖 user 覆盖 local),你以为在用公司的 GitHub server,实际跑的是个人的。
排查命令:
claude mcp get github
# 输出会显示该 server 当前生效在哪个 scope纪律:server 名带环境后缀,如 github-prod github-personal,物理隔离不同凭据。绝不用 github 这种裸名。
15.9.3 敏感凭据写进 .mcp.json 提交 Git(红线)
这是最严重的一条,触碰第 05 章的红线边界。.mcp.json 里 env 字段的 token、连接串里的密码,一旦 git commit 推到远程仓库,等同于公开泄露。即使仓库是 private,也会被 GitHub 的 secret scanning 扫到并通知管理员,且 Git 历史里永久留存。
正确做法(三选一):
方案一:凭据走 user scope,不进仓库
# 个人凭据写到 user scope,不进 .mcp.json
claude mcp add --scope user --transport stdio github -- \
npx -y @modelcontextprotocol/server-github \
-e GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx
# 项目 .mcp.json 只写不含凭据的 server,或用占位符方案二:.mcp.json 用环境变量引用,配 .env
部分 server 支持从进程环境读凭据(不写进 env 字段),此时 .mcp.json 只声明 server,凭据由 shell 或 .env 注入,.env 加入 .gitignore:
{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"]
}
}
}# .env(加入 .gitignore)
GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx方案三:OAuth 托管 server,凭据不落地
Sentry、Linear、Notion 等支持 OAuth 的托管 server,token 由 Claude Code 本地管理,不进 .mcp.json。配置时只写 url,首次用 /mcp 走浏览器授权。
已泄露的补救:立即在对应平台 revoke token(GitHub Settings → Developer settings → Personal access tokens → Revoke),轮换密码,然后用 git filter-repo 清理历史(破坏性操作,需团队协调,属第 05 章红线,执行前必须二次确认)。
15.10 小结
MCP 把 Claude Code 从"能跑 Bash 的聊天机器人"升级成"能直连外部系统的 Agent"。四个要点记住即可:
- 传输选型:本地 stdio,远程 http,其余两种只在特殊场景用
- 配置位置:团队共享走
.mcp.json(project scope),个人凭据走 user scope - 工具命名:
mcp__<server>__<tool>,起名别冲突 - 三者分工:MCP 给工具,Skill 给流程,Hook 给时机
下一章我们讲 Skills——把一套工作流指令封装成 /skill-name 可调用的能力包,与本章 MCP 工具组合使用,是 Claude Code 扩展能力的完整形态。
下一章:Skills 与自定义命令