MCP连接外部工具
> **时效说明**:本文内容以 2026 年 3 月为基准。
Claude Code:MCP:连接外部工具
时效说明:本文内容以 2026 年 3 月为基准。
10.1 MCP——让Claude连接外部工具
MCP全称Model Context Protocol,是Anthropic推出的一个开放协议,定义了AI模型和外部工具之间怎么通信。你可以把它理解成AI世界的JDBC——JDBC定义了Java程序和数据库怎么通信,MCP定义了AI和各种外部服务怎么通信。
协议本身是开放的,已经有大量第三方实现了各种MCP Server:GitHub、PostgreSQL、MySQL、Sentry、Slack、Jira……这些Server暴露出来的能力,Claude可以直接调用。
MCP诞生之前,AI助手能做的事局限在它的上下文窗口里——你把代码贴进来,它帮你分析;你把报错贴进来,它帮你排查。它自己连不上任何外部系统。MCP改变了这个局面,让AI可以主动去拉数据、执行操作,变成一个真正意义上的AI代理。
10.2 MCP的工作原理
理解原理能帮你在遇到问题时更快定位。
10.2.1 Server/Client架构
MCP采用标准的Client/Server架构:
- MCP Server:一个独立运行的进程,负责封装某个外部系统的能力(比如把GitHub API包成标准MCP工具)
- MCP Client:Claude Code本身扮演Client角色,通过标准协议调用Server暴露的工具
- 通信方式:支持两种——stdio(标准输入输出,本地进程间通信)和HTTP/SSE(网络通信,适合远程Server)
整个调用链是这样的:
你对Claude说话
→ Claude决定要调用某个外部工具
→ Claude Code(Client)通过MCP协议向Server发送工具调用请求
→ MCP Server收到请求,调用真实的外部API(GitHub/数据库/Slack等)
→ Server把结果返回给Claude Code
→ Claude看到结果,继续和你对话
10.2.2 工具是如何暴露的
每个MCP Server启动后,会向Claude Code注册它支持的工具列表。每个工具都有:
- 名称:比如
create_issue、query_database - 描述:用自然语言说明这个工具做什么,Claude根据描述判断什么时候调用
- 参数定义:JSON Schema格式,指定需要哪些参数
Claude会把所有已连接Server的工具列表加入自己的上下文。当你问"帮我创建一个GitHub Issue"时,Claude看到有一个叫create_issue的工具,描述是"在GitHub仓库创建Issue",它就会调用这个工具。
10.2.3 什么时候用stdio,什么时候用HTTP
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 本地开发环境,MCP Server跑在本机 | stdio | 延迟最低,无网络开销 |
| 团队共享的MCP Server,部署在内网服务器 | HTTP | 多人共用,统一管理 |
| 第三方SaaS提供的MCP服务 | HTTP | Server不在你控制下 |
| 需要认证的企业内部系统 | HTTP + Token | 有完整的安全控制 |
大多数开源MCP Server(GitHub、数据库这些)都是stdio方式,通过npx启动一个本地进程。
10.3 为什么需要MCP
在没有MCP之前,你想让Claude帮你分析一个GitHub PR,流程是这样的:你自己去GitHub复制PR的diff,粘贴到对话里,Claude分析完给你建议,你再回去GitHub做操作。
有了MCP之后,你直接说"帮我看一下#234这个PR有什么问题,然后写一条review评论",Claude自己去拉PR内容,分析完直接调用GitHub API提交评论,整个过程你不用动手。
对Java后端开发者来说,最有价值的几个连接是:
- GitHub:代码仓库操作(PR review、Issue管理、代码搜索)
- 数据库:直接查生产数据,分析问题
- Sentry:直接看报错堆栈,不用复制粘贴
- Jira:查看任务状态、更新进度
- Slack:通知机器人、查看频道消息
这几个覆盖了日常排查问题和项目管理80%的信息需求。
10.4 配置方式
10.4.1 命令行快速添加
命令行添加是最快的方式,适合个人本地使用:
# 添加GitHub MCP(通过npx运行)
claude mcp add github -- npx @modelcontextprotocol/server-github
# 添加MySQL MCP
claude mcp add mysql -- npx @modelcontextprotocol/server-mysql
# 添加HTTP类型的MCP
claude mcp add --transport http myserver https://mcp.example.com
# 查看已添加的MCP列表
claude mcp list
# 删除某个MCP
claude mcp remove github
命令行配置只存在你本地(存储在~/.claude/settings.json),团队协作的时候更推荐用项目级配置文件.mcp.json,提交到git仓库,所有人共享同一套工具配置。
10.4.2 .mcp.json完整配置示例
将以下配置文件放在项目根目录,命名为.mcp.json。
GitHub + MySQL + Sentry基础配置:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
},
"mysql": {
"command": "npx",
"args": ["-y", "@benborla29/mcp-server-mysql"],
"env": {
"MYSQL_HOST": "localhost",
"MYSQL_PORT": "3306",
"MYSQL_USER": "readonly_user",
"MYSQL_PASSWORD": "${DB_PASSWORD}",
"MYSQL_DATABASE": "myapp_prod",
"MYSQL_ALLOW_INSERT_OPERATION": "false",
"MYSQL_ALLOW_UPDATE_OPERATION": "false",
"MYSQL_ALLOW_DELETE_OPERATION": "false"
}
},
"sentry": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sentry"],
"env": {
"SENTRY_AUTH_TOKEN": "${SENTRY_TOKEN}",
"SENTRY_ORG": "your-org-slug"
}
}
}
}
10.5 更多MCP Server配置示例
10.5.1 PostgreSQL配置
PostgreSQL有官方维护的MCP Server,连接生产库时务必用只读账号:
{
"mcpServers": {
"postgresql": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"POSTGRES_CONNECTION_STRING": "postgresql://readonly_user:${PG_PASSWORD}@localhost:5432/myapp_prod"
}
}
}
}
连接好之后可以直接问Claude:"帮我查一下users表里最近7天注册但是没有完成邮箱验证的用户数量",Claude会直接执行SQL并返回结果。
PostgreSQL MCP Server暴露的主要工具:
query:执行只读SQL查询list_tables:列出数据库里的所有表describe_table:查看某个表的字段定义
10.5.2 Redis配置
Redis的MCP Server可以让Claude直接操作缓存,对排查缓存相关问题很有用:
{
"mcpServers": {
"redis": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-redis"],
"env": {
"REDIS_URL": "redis://:${REDIS_PASSWORD}@localhost:6379/0"
}
}
}
}
典型使用场景:"帮我看一下缓存里session开头的key有多少个,内存占用大概是多少"、"这个用户的购物车数据在Redis里是什么结构"。
生产Redis建议:
- 只给
KEYS、GET、TYPE、TTL等只读命令的权限 - 不要给
FLUSHDB、DEL等破坏性命令权限 - 如果Redis没有密码,强烈建议先加上,不要裸连
10.5.3 Jira配置
Jira的MCP Server让Claude可以直接查看和操作项目任务:
{
"mcpServers": {
"jira": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-jira"],
"env": {
"JIRA_URL": "https://your-company.atlassian.net",
"JIRA_EMAIL": "your-email@company.com",
"JIRA_API_TOKEN": "${JIRA_TOKEN}"
}
}
}
}
Jira API Token在Atlassian账户设置里生成,不是密码。有了这个配置之后:
- "帮我列一下本周还没关闭的Bug,按优先级排序"
- "把PROJ-456的状态改成In Progress,并加一条评论说正在处理"
- "给这次sprint生成一个进度报告"
Claude都能直接操作。
10.5.4 Slack配置
Slack MCP Server可以让Claude查看频道消息、发送通知:
{
"mcpServers": {
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}",
"SLACK_TEAM_ID": "T01234567"
}
}
}
}
Slack Bot Token在Slack App管理后台创建。权限建议只开:
channels:read:读取频道列表chat:write:发送消息messages:read:读取消息(可选)
不要开files:write、admin.*这些高权限。
典型场景:"部署完成后自动发一条消息到#backend-deploys频道"、"帮我查一下上周#backend频道里有没有关于数据库超时的讨论"。
10.5.5 本地文件系统(特殊权限目录)
有时候你需要让Claude访问某个特定的目录,比如你的文档库或者配置文件仓库,但不想给它整个文件系统权限:
{
"mcpServers": {
"docs-library": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/you/company-docs",
"/Users/you/api-specs"
]
}
}
}
@modelcontextprotocol/server-filesystem接受多个路径参数,Claude只能访问这些目录,访问其他路径会被拒绝。适合把公司内部文档库挂进来,让Claude能查阅文档来回答问题。
10.6 自己写一个简单的MCP Server
第三方MCP Server不一定覆盖你的所有需求。如果你有公司内部的API(比如自研的部署平台、监控系统),可以自己写一个MCP Server把它暴露给Claude。
下面是一个用Python写的MCP Server示例,暴露了公司内部部署平台的两个接口。
10.6.1 安装依赖
pip install mcp httpx
mcp是官方Python SDK,httpx是异步HTTP客户端。
10.6.2 完整MCP Server代码
#!/usr/bin/env python3
"""
公司内部部署平台的MCP Server
暴露两个工具:
- get_deploy_status:查看某个服务的部署状态
- trigger_deploy:触发一次部署
"""
import asyncio
import json
from typing import Any
import httpx
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
# 公司内部部署平台的API地址(从环境变量读取,不硬编码)
import os
DEPLOY_API_BASE = os.environ.get("DEPLOY_API_BASE", "https://deploy.internal.company.com")
DEPLOY_API_TOKEN = os.environ.get("DEPLOY_API_TOKEN", "")
# 创建MCP Server实例
server = Server("deploy-platform")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
"""向Claude注册这个Server提供的工具列表"""
return [
types.Tool(
name="get_deploy_status",
description=(
"查询指定服务的最新部署状态,包括部署时间、部署人、版本号、当前状态(成功/失败/进行中)。"
"当用户问某个服务的部署情况、上线状态、当前版本时调用。"
),
inputSchema={
"type": "object",
"properties": {
"service_name": {
"type": "string",
"description": "服务名称,例如 user-service、order-service"
},
"environment": {
"type": "string",
"enum": ["dev", "staging", "prod"],
"description": "环境,默认是prod",
"default": "prod"
}
},
"required": ["service_name"]
}
),
types.Tool(
name="trigger_deploy",
description=(
"触发指定服务的部署。会用指定的镜像版本或者Git Tag发起一次部署。"
"注意:只能部署到dev和staging环境,prod环境需要在部署平台手动确认。"
),
inputSchema={
"type": "object",
"properties": {
"service_name": {
"type": "string",
"description": "要部署的服务名称"
},
"version": {
"type": "string",
"description": "镜像Tag或者Git Tag,例如 v1.2.3 或 latest"
},
"environment": {
"type": "string",
"enum": ["dev", "staging"],
"description": "目标环境(只支持dev和staging)"
},
"reason": {
"type": "string",
"description": "部署原因,会记录在部署日志里"
}
},
"required": ["service_name", "version", "environment"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.TextContent]:
"""处理Claude发来的工具调用请求"""
if name == "get_deploy_status":
return await _get_deploy_status(
service_name=arguments["service_name"],
environment=arguments.get("environment", "prod")
)
elif name == "trigger_deploy":
return await _trigger_deploy(
service_name=arguments["service_name"],
version=arguments["version"],
environment=arguments["environment"],
reason=arguments.get("reason", "Claude触发的部署")
)
else:
return [types.TextContent(
type="text",
text=f"未知工具:{name}"
)]
async def _get_deploy_status(service_name: str, environment: str) -> list[types.TextContent]:
"""调用部署平台API查询状态"""
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"{DEPLOY_API_BASE}/api/v1/deployments/{service_name}",
params={"env": environment},
headers={"Authorization": f"Bearer {DEPLOY_API_TOKEN}"},
timeout=10.0
)
response.raise_for_status()
data = response.json()
# 格式化成易读的文本返回给Claude
result = (
f"服务:{service_name}({environment} 环境)\n"
f"状态:{data['status']}\n"
f"版本:{data['version']}\n"
f"部署时间:{data['deployed_at']}\n"
f"部署人:{data['deployed_by']}\n"
f"提交信息:{data.get('commit_message', 'N/A')}"
)
except httpx.HTTPStatusError as e:
result = f"查询失败:HTTP {e.response.status_code},{e.response.text}"
except httpx.TimeoutException:
result = "查询超时,部署平台可能暂时不可用"
except Exception as e:
result = f"查询出错:{str(e)}"
return [types.TextContent(type="text", text=result)]
async def _trigger_deploy(
service_name: str,
version: str,
environment: str,
reason: str
) -> list[types.TextContent]:
"""调用部署平台API触发部署"""
# 安全检查:不允许部署到prod
if environment == "prod":
return [types.TextContent(
type="text",
text="[拒绝] prod环境部署需要在部署平台手动确认,无法通过API触发。"
)]
async with httpx.AsyncClient() as client:
try:
response = await client.post(
f"{DEPLOY_API_BASE}/api/v1/deployments",
json={
"service": service_name,
"version": version,
"environment": environment,
"reason": reason
},
headers={"Authorization": f"Bearer {DEPLOY_API_TOKEN}"},
timeout=30.0
)
response.raise_for_status()
data = response.json()
result = (
f"部署已触发!\n"
f"部署ID:{data['deploy_id']}\n"
f"服务:{service_name} → {environment}\n"
f"版本:{version}\n"
f"预计完成时间:{data.get('estimated_duration', '3-5分钟')}\n"
f"查看进度:{DEPLOY_API_BASE}/deployments/{data['deploy_id']}"
)
except Exception as e:
result = f"部署触发失败:{str(e)}"
return [types.TextContent(type="text", text=result)]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())
10.6.3 把自定义Server加入.mcp.json
{
"mcpServers": {
"deploy-platform": {
"command": "python3",
"args": ["/path/to/your/deploy_mcp_server.py"],
"env": {
"DEPLOY_API_BASE": "https://deploy.internal.company.com",
"DEPLOY_API_TOKEN": "${DEPLOY_TOKEN}"
}
}
}
}
配置好之后,Claude就能直接说"帮我把user-service的v1.2.3部署到staging",它会调用你的MCP Server,Server转发到公司内部API,真实地触发一次部署。
10.6.4 description字段的重要性
工具描述(description字段)直接决定Claude什么时候会调用这个工具。写得好,Claude能准确识别时机;写得差,Claude不知道什么时候用,或者用错。
好的description应该包含:
- 工具的核心功能(一句话)
- 什么时候应该调用(关键词举例)
- 什么时候不该调用(避免误触发)
# 差的描述(太模糊)
description="Gets deployment info"
# 好的描述(明确触发场景)
description=(
"查询指定服务的最新部署状态,包括部署时间、部署人、版本号、当前状态。"
"当用户询问某个服务是否已上线、最新版本是什么、谁部署的时调用。"
"不要用于查询代码本身,那应该用GitHub工具。"
)
10.7 MCP工具调用的完整交互流程
了解完整的调用链,遇到问题时能更快定位。
场景:你说"帮我查一下user-service现在生产环境是哪个版本"
- Claude收到你的问题,分析上下文
- Claude查看当前连接的MCP Server工具列表,发现
deploy-platform下有个get_deploy_status工具,描述里提到"查询服务当前版本" - 弹出确认提示(默认行为):code
Claude想要使用工具:get_deploy_status 参数:{"service_name": "user-service", "environment": "prod"} [允许] [拒绝] [本次会话全部允许] - 你点击"允许"
- Claude Code把工具调用请求发给deploy-platform MCP Server
- MCP Server调用部署平台API,拿到结果
- 结果返回给Claude:"user-service,prod环境,版本v1.5.2,2026-03-28 14:30部署,部署人:张三"
- Claude把这个信息整合进回复,告诉你
整个流程对你来说是透明的,但了解它能帮你在步骤5-6卡住时知道去哪里排查。
10.8 MCP的安全最佳实践
10.8.1 最小权限原则
每个MCP Server只给它需要的最小权限,不要图方便给一个管理员Token什么都能干。
{
"mcpServers": {
"github": {
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
GitHub Token的权限范围选择:
- 只需要读PR、Issue:开
read:repo - 需要提交评论:开
write:discussion - 不要开
delete_repo、admin:org这些危险权限
数据库同理,生产库只给只读账号:
-- 为Claude专门创建只读账号
CREATE USER 'claude_readonly'@'localhost' IDENTIFIED BY '${password}';
GRANT SELECT ON myapp_prod.* TO 'claude_readonly'@'localhost';
-- 不要给INSERT、UPDATE、DELETE、DROP权限
FLUSH PRIVILEGES;
10.8.2 敏感数据处理
有几类数据要特别小心:
密码和个人信息:如果数据库里有password、phone、id_card这些字段,在MCP Server层面做脱敏,不要让它们原样传给Claude。
# 在自定义MCP Server里做脱敏
def mask_sensitive_fields(row: dict) -> dict:
sensitive_keys = {'password', 'phone', 'id_card', 'bank_account', 'email'}
return {
k: '***MASKED***' if k.lower() in sensitive_keys else v
for k, v in row.items()
}
大量数据:让Claude一次查几百万条数据既没意义又危险,在MCP Server里限制查询结果数量:
# 强制限制SELECT查询最多返回1000行
def safe_query(sql: str) -> str:
sql = sql.strip().rstrip(';')
if 'LIMIT' not in sql.upper():
sql += ' LIMIT 100'
return sql
只读原则:数据库MCP Server除非有明确需求,否则只开只读工具。写操作(INSERT/UPDATE/DELETE)要显式声明,在工具描述里说明,方便Claude在决策时更谨慎。
10.8.3 审计日志
所有MCP工具调用都应该有日志,方便事后审计。可以在自定义MCP Server里集中记录:
import logging
from datetime import datetime
# 配置审计日志
audit_logger = logging.getLogger('mcp.audit')
audit_logger.setLevel(logging.INFO)
handler = logging.FileHandler('/var/log/mcp-audit.log')
handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
audit_logger.addHandler(handler)
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
# 记录每一次工具调用
audit_logger.info(json.dumps({
"timestamp": datetime.now().isoformat(),
"tool": name,
"arguments": _redact_sensitive(arguments), # 脱敏后记录
"caller": "claude-code"
}))
# 正常处理...
日志格式用JSONL,方便用jq查询:
# 查看今天所有trigger_deploy调用
jq 'select(.tool == "trigger_deploy")' /var/log/mcp-audit.log
# 统计今天数据库查询次数
jq 'select(.tool == "query") | .timestamp' /var/log/mcp-audit.log | wc -l
10.8.4 .mcp.json的安全处理
.mcp.json会提交到git仓库,所以:
- 绝对不要在这个文件里写明文密码,全部用
${ENV_VAR}引用 - 把密钥存在本地的
.env文件,加到.gitignore - CI/CD环境通过Secrets管理(GitHub Secrets、Vault等)注入
# .env文件(加到.gitignore)
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
DB_PASSWORD=your_password_here
DEPLOY_TOKEN=your_token_here
# 加载.env后启动Claude
source .env && claude
# 或者用direnv自动加载:https://direnv.net/
10.9 MCP调试:排查连接失败
10.9.1 常见错误和解决方法
错误1:MCP Server启动失败
症状:Claude提示"无法连接到 xxx MCP Server"
排查步骤:
# 直接在终端手动启动MCP Server,看有没有报错
npx @modelcontextprotocol/server-github
如果报错,常见原因:
- npx没装或版本太旧:
npm install -g npx - 网络问题,npx无法下载包:检查代理设置
- 环境变量没设置:检查
GITHUB_PERSONAL_ACCESS_TOKEN是否存在
错误2:工具调用返回权限错误
症状:Claude调用工具,返回"401 Unauthorized"或"403 Forbidden"
排查步骤:
# 直接测试Token是否有效
curl -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user
如果返回用户信息,Token有效但权限不足;如果返回401,Token本身失效了,需要重新生成。
错误3:.mcp.json配置不生效
症状:Claude完全不知道MCP工具的存在
排查步骤:
# 验证JSON语法
python3 -c "import json; json.load(open('.mcp.json')); print('JSON语法正确')"
# 确认文件在项目根目录
ls -la .mcp.json
# 重启Claude Code(配置文件在启动时读取,改了之后需要重启)
错误4:自定义Python MCP Server没响应
在自定义Server的main()里加日志,看是否正常接收到请求:
async def main():
import sys
print("MCP Server 启动中...", file=sys.stderr)
async with stdio_server() as (read_stream, write_stream):
print("等待连接...", file=sys.stderr)
await server.run(
read_stream,
write_stream,
server.create_initialization_options()
)
注意:调试信息必须输出到stderr,不能输出到stdout(stdout是MCP协议通信用的)。
10.9.2 MCP Server的本地测试
在没有Claude的情况下测试你的MCP Server是否正常工作:
# 安装MCP Inspector工具
npm install -g @modelcontextprotocol/inspector
# 启动Inspector,它会连接你的MCP Server并提供调试界面
mcp-inspector python3 /path/to/your_server.py
Inspector会在本地起一个Web界面,你可以在里面手动调用工具,验证返回值,不需要依赖Claude来触发。