核心概念
MCP - Model Context Protocol - 模型上下文协议
核心是定义了一套统一的接口,类似于 USB 接口,只要支持 MCP 协议,就可以接入
MCP 协议可概括为:以 JSON-RPC 2.0 为通信基础,通过能力协商来提供 资源(Resources)、工具(Tools)、提示(Prompts) 三大原语的标准化协议
基础协议(Base Protocol)
所有客户端和服务端之间的消息都必须遵循 JSON-RPC 2.0 规范,并定义了三种消息类型:
- 请求(Requests):发起操作,必须包含唯一的
id和method,对方会回送一个携带相同id的结果或错误响应 - 响应(Responses):作为对请求的答复,分为成功结果和错误响应两类
- 通知(Notifications):单向传递,接收方无需恢复,因此不能包含
id字段
生命周期(Lifecycle)
MCP 连接严格遵循 初始化(Initialization) -> 操作(Operation) -> 关闭(Shutdown) 的三阶段状态机
- 初始化:客户端主动发送
initialize请求,交互进行协议版本选择和能力协商,服务器响应后,客户端必须再发一个initialized通知,会话才算正式建立 - 操作:双方能力确认后即可自由交换信息,如调用工具、读取资源等
- 关闭:任务完成后,由任一方的传输层发起并释放资源
能力协商
能力协商是 MCP 声明周期里初始化的核心环节,它发生在客户端和服务端建立连接后的“握手”阶段
| 角色 | 动作 | 携带的关键信息 | 目的 |
|---|---|---|---|
| 客户端 | 发送 initialize 请求 | protocolVersion, capabilities (如sampling, roots) | “我支持这些,我们用这个版本聊” |
| 服务端 | 返回 initialize 响应 | protocolVersion, capabilities (如tools, resources, listChanged) | “同意用这个版本。我能提供这些功能,你看看” |
| 客户端 | 发送 initialized 通知 | (无实质性内容) | “没问题,我们开始正式通信吧” |
服务端与客户端功能
服务端主要提供三类原语,客户端负责逆向交互:
服务端三大原语:
- Resources:资源,暴露数据,读文件、查数据库表
- Tools:工具,可执行操作,计算、发邮件、搜索网页
- Prompts:提供预设的提示词模板
客户端功能:
- 采样(Sampling):服务器请求客户端内的 LLM 生成内容
- 根目录(Roots):服务器主动询问客户端哪些文件或 URI 边界可以操作
- 信息获取(Elicitation):服务器主动向用户请求交互信息
安全模型(Security Model)
MCP 将 用户同意与控制 放在首位,所有访问和操作都需要用户明确了解并授权
- 用户同意与控制(核心原则)
- 认证与授权(OAuth 2.0)
- 传输层安全
- 服务端边界隔离
- 客户端职责
传输机制(Transports)
MCP 定义了两种标准传输方式:
stdio:客户端作为子进程启动服务器,通过标准输入输出通信,适合本地工具Streamable HTTP:类似标准的 Web API,用于远端服务
对比分析
| 对比维度 | stdio (标准输入输出) | Streamable HTTP (流式HTTP) |
|---|---|---|
| 底层原理 | 子进程的 stdin/stdout 管道。客户端直接启动服务端进程 | HTTP 的 POST 请求和 SSE (Server-Sent Events) 事件流。服务端独立运行 |
| 连接方向 | 客户端主动启动并连接到服务端 | 客户端连接到已运行的服务端的 URL |
| 部署模式 | 本地跟随客户端。服务端生命周期由客户端管理 | 独立网络服务。需自行部署、运维,客户端随时可连 |
| 适用场景 | 个人本地开发、桌面应用、CI/CD 中的一次性任务 | 生产环境、多用户共享、跨设备访问、微服务架构 |
| 多客户端 | 一对一,一个进程只服务一个客户端 | 天生多对一,一个服务可同时处理多个客户端请求 |
| 网络要求 | 无,纯本地进程间通信 | 必须有,需要内网或公网可达的 IP/域名和端口 |
| 配置项 | command (启动命令)、args (参数) | url (服务地址)、headers (鉴权) |
| 生命周期 | 客户端管理:启动连接时创建,断开连接时销毁 | 独立管理:需手动启动、停止、监控 |
| 鉴权方式 | 通常无需,但某些 Server 会读取环境变量 (如 env.GITHUB_TOKEN) | 靠 headers 传 Authorization: Bearer 或 OAuth 验证。 |
Streamable HTTP 协议细节
- SSE 建连、会话管理、
/message端点、连接恢复机制
JSON-RPC 2.0 规范
JSON-RPC 2.0 可以理解为一种标准化的 “函数调用” 方式,它允许一个程序通过网络或进程间通信,请求另一个程序执行特定的方法(函数),并获得计算结果,核心就是定义了一套结构简单、语言无关的请求与响应格式
请求对象(Request)
客户端/服务端发送一个请求对象(Request Object)来发起一次远程调用,包含以下字段:
jsonrpc:必填,值为固定字符串2.0,用于表示协议版本id:必填,特殊情况除外,由客户端/服务端生成的唯一标识符,可以是字符串、数字或nullmethod:必填,要调用的远程方法名params:可选,调用方法的参数,可以是位置区分的数组,或以名称区分的对象
响应对象(Response)
客户端/服务端收到请求后,必须返回一个响应对象,且只包含 result 或 error 中的一个
成功响应
jsonrpc:2.0id:必填,与发起请求的id一致result:必填,包含方法调用的返回值,具体内容由方法定义
--> 请求: {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- 响应: {"jsonrpc": "2.0", "result": 19, "id": 1}
错误响应
当调用失败时,响应必须包含一个 error 对象,其内部结构为:
code:必填,指示错误类型的message:必填,简单的错误描述字符串data:可选,包含附加错误信息的基本或结构化类型
JSON-RPC 2.0 定义的常用错误代码及其含义:
| 错误代码 | 含义 | 说明 |
|---|---|---|
| -32700 | 解析错误 | 服务器收到的不是有效的 JSON 文本 |
| -32600 | 无效请求 | 发送的 JSON 数据不是一个有效的请求对象,如缺失 method 字段 |
| -32601 | 方法未找到 | 请求的 method 方法在服务端不存在 |
| -32602 | 无效的参数 | 提供给方法的 params 参数无效或格式错误 |
| -32603 | 内部错误 | 服务端内部发生的通用错误,非业务逻辑问题 |
| -32000 to -32099 | 服务器错误 | 此范围保留用于实现自定义的服务端错误,如数据库连接失败等 |
通知(Notification)
通知是一种特殊的请求,它没有 id 字段,不允许对通知做出任何响应
批量请求(Batch Request)
为了减少网络请求次数,客户端可以一次性发送一个包含多个请求对象的数组,服务器会并发处理这些请求,并返回一个响应数据:
--> [
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "notify", "params": ["hello"], "id": "2"},
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "3"}
]
<-- [
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "3"}
]
MCP 通信
- 传输层
- 本地进程 (
stdio) - 远程连接 (
Streamable HTTP) - 保活(双向)
- 重连策略
- 本地进程 (
- 客户端主动 - 请求/响应 (客户端 → 服务端 / 有去有回带
id/ JSON-RPC 2.0 请求)- 调用工具 (
tools/call) - 读取资源 (
resources/read) - 获取提示 (
prompts/get) - 初始化 (
initialize) - 列出工具/资源/提示 (
tools/list,resources/list,prompts/list) - Ping 请求
- 自动补全请求 (
completion/complete)
- 调用工具 (
- 客户端主动 - 通知 (客户端 → 服务端 / 有去无回无
id/ JSON-RPC 2.0 通知)- 初始化完成 (
notifications/initialized) - 取消请求 (
notifications/cancelled) - 根目录变更 (
notifications/roots/list_changed)
- 初始化完成 (
- 服务端反向 - 请求 (服务端 → 客户端 / 有去有回带
id/ JSON-RPC 2.0 请求)- 采样,请求 LLM 生成内容 (
sampling/createMessage) - 信息获取,向人类用户收集信息 (
elicitation/create) - 根目录,询问根目录 (
roots/list) - Ping 请求
- 采样,请求 LLM 生成内容 (
- 服务端反向 - 进度通知 (服务端 → 客户端 / 有去无回无
id/ JSON-RPC 2.0 通知)- 报告长任务进展 (
notifications/progress)
- 报告长任务进展 (
- 服务端通知 - 列表变更 (服务端 → 客户端 / 有去无回无
id/ JSON-RPC 2.0 通知)- 工具列表变更 (
notifications/tools/list_changed) - 资源列表变更 (
notifications/resources/list_changed) - 提示列表变更 (
notifications/prompts/list_changed) - 资源内容更新 (
notifications/resources/updated)
- 工具列表变更 (
客户端到服务端
读取资源 Resources
- 方法:
resources/read
调用工具 Tools
- 方法:
tools/call
获取提示 Prompts
- 方法:
prompts/get
服务端到客户端
采样 Sampling(Request)
- 方法:
sampling/createMessage - MCP 服务端请求客户端,使用 大模型 生成内容
- 智能摘要
- 动态生成
- 多步推理
- 服务端请求客户端生成 LLM 内容时的完整交互流程、权限控制、与"用户同意"的关系
// 服务端 --> 客户端 (请求采样)
{
"jsonrpc": "2.0",
"method": "sampling/createMessage",
"params": {
"messages": [
{"role": "user", "content": "请用一句话总结这段日志:错误率突然飙升后自动恢复..."}
],
"maxTokens": 100
},
"id": 5
}
// 客户端 --> 服务端 (返回生成结果)
{
"jsonrpc": "2.0",
"result": {
"role": "assistant",
"content": "系统经历了短暂错误率上升,现已自动恢复正常。"
},
"id": 5
}
信息获取 Elicitation(Request)
- 方法:
elicitation/create - MCP 服务端请求客户端,从 用户 收集信息
- 确认选项
- 补充参数
- 多因素认证
- 服务端向人类用户请求输入时的表单定义、选项类型、用户取消的处理
// 服务端请求获取用户信息
{
"jsonrpc": "2.0",
"method": "elicitation/create",
"params": {
"messages": [
{ "role": "user", "content": "请选择要处理的分支:" }
],
"options": [
{ "label": "main", "value": "main" },
{ "label": "develop", "value": "develop" },
{ "label": "feature/new-ui", "value": "feature/new-ui" }
]
},
"id": 22
}
// 客户端返回用户的选择
{
"jsonrpc": "2.0",
"result": {
"action": "accept", // 或 "decline"/"cancel"
"content": "feature/new-ui"
},
"id": 22
}
进度通知 Progress(Notification)
- 方法:
notifications/progress - 服务端主动、持续的向客户端报告任务进展
- 前置条件:
- 客户端发起请求时,声明自己支持进度,通过在
params中添加一个_meta的字段,内含一个唯一标识符,命名为progressToken
- 客户端发起请求时,声明自己支持进度,通过在
- 服务端在处理过程中,发送进度通知,带上相同的
progressToken
- 前置条件:
- 进度值的含义约定、客户端如何展示、
total字段的语义
// 客户端请求,声明可接收进度
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "long_report",
"_meta": { "progressToken": "token-abc-123" }
},
"id": 10
}
// 服务端通知进度
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "token-abc-123",
"progress": 45,
"total": 100,
"message": "正在处理第 45/100 个文件..."
}
}
心跳机制(PING)
自动补全(Completion)
MCP Server 服务端
Resources 资源定义
方法 resources/list 返回的标准格式:
{
"resources": [
{
"uri": "bookmarks://list",
"name": "所有书签",
"description": "返回所有已保存的书签列表",
"mimeType": "application/json"
}
]
}
字段:
- uri:必填,资源的唯一标识
- name:必填,名称
- description:可选,描述资源的内容
- mimeType:可选,资源的数据类型,客户端据此决定如何渲染
Tools 工具定义
方法 tools/list 返回的标准格式:
{
"tools": [
{
"name": "add_bookmark",
"description": "添加一个书签,需要提供标题和URL",
"inputSchema": {
"type": "object",
"properties": {
"title": { "type": "string", "description": "书签标题" },
"url": { "type": "string", "description": "书签URL" }
},
"required": ["title", "url"]
}
}
]
}
字段:
- name:必填,工具的唯一标识,客户端和大模型通过该名称来调用
- description:必填,告诉大模型该工具是干什么的,直接影响调用准确率
- inputSchema:必填,为 JSON Schema 格式,描述调用时需要什么参数、参数类型是什么、哪些必填
Prompts 提示定义
Prompts 提示定义比较特殊,提供了 定义(发现) 和 获取(使用) 两个标准格式
Prompt 的两层标准格式
定义格式(用于列表发现)
当客户端发起 prompts/list 请求时,服务端返回的 Prompt 列表,定义结构如下:
- name:必填,唯一标识符,客户端通过该名称获取具体内容
- title:可选,显示名称
- dscription:可选,描述 Prompt 的用途
- argument:可选,参数字段,用于动态自定义 Prompt 的内容
获取格式(真正的标准格式)
{
"description": "可选的整体描述",
"messages": [
{
"role": "user", // 角色,user 或 assistant
"content": { // 内容对象,有多种类型
"type": "text",
"text": "具体的提示词文本..."
}
}
]
}
Prompt 可以由多条消息组成,每条消息都有明确的 role 和 content,其中 content 的类型支持多种格式:
- 文本 (
text):最常见的形式 - 图片 (
image):Base64 编码,可实现多模态交互 - 音频 (
audio):Base64 编码 - 嵌入资源 (
resource):直接将服务端的某个资源内容嵌入到消息中,比如引用一段代码或文件
集成认证
- 静态 API Key:内部工具、开发环境,一次配置长期有效
- OAuth 2.0 集成:多用户 Saas 平台、需权限分级场景,浏览器登录
- 基于 OAuth 的完整认证授权流程,服务端如何注册授权服务器、客户端如何获取 token
- mTLS(双向 TLS 认证):高安全企业内网,零信任架构,对用户透明(证书自动验证)
示例
使用 Python 和 Java(基于 Spring AI)两种编程语言开发一个 MCP Server 服务端
Python
# 按照依赖 pip install mcp
# server.py
import json
from typing import Any
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource, ResourceTemplate
# ---------- 数据层(内存存储) ----------
bookmarks: list[dict[str, str]] = []
# ---------- 创建 Server 实例 ----------
server = Server("bookmark-server")
# ==================== 工具定义 ====================
# 由 mcp 的 Python SDK 提供的装饰器
@server.list_tools()
async def list_tools() -> list[Tool]:
"""告诉客户端:我有哪些工具可用"""
return [
Tool(
name="add_bookmark",
description="添加一个书签,需要提供标题和URL",
inputSchema={
"type": "object",
"properties": {
"title": {"type": "string", "description": "书签标题"},
"url": {"type": "string", "description": "书签URL"}
},
"required": ["title", "url"]
}
),
Tool(
name="search_bookmarks",
description="按关键词搜索书签",
inputSchema={
"type": "object",
"properties": {
"keyword": {"type": "string", "description": "搜索关键词"}
},
"required": ["keyword"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
"""处理工具调用"""
if name == "add_bookmark":
bookmarks.append({
"title": arguments["title"],
"url": arguments["url"]
})
return [TextContent(
type="text",
text=f"✅ 书签已添加: {arguments['title']} ({arguments['url']})"
)]
elif name == "search_bookmarks":
keyword = arguments["keyword"].lower()
results = [b for b in bookmarks if keyword in b["title"].lower()]
if not results:
return [TextContent(type="text", text="未找到匹配的书签。")]
return [TextContent(
type="text",
text="\n".join([f"- {b['title']}: {b['url']}" for b in results])
)]
raise ValueError(f"未知工具: {name}")
# ==================== 资源定义 ====================
@server.list_resources()
async def list_resources() -> list[Resource]:
"""告诉客户端:我有哪些资源可读"""
return [
Resource(
uri="bookmarks://list",
name="所有书签",
description="返回所有已保存的书签列表",
mimeType="application/json"
)
]
@server.read_resource()
async def read_resource(uri: str) -> str:
"""读取资源内容"""
if uri == "bookmarks://list":
return json.dumps(bookmarks, ensure_ascii=False, indent=2)
raise ValueError(f"未知资源: {uri}")
# ==================== 示例集成鉴权中间件,使用静态 API Key ====================
# 实际项目中应从环境变量读取
API_KEY = "your-secret-api-key-change-me"
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# 健康检查路径跳过鉴权
if request.url.path in ("/health", "/ping"):
return await call_next(request)
# 检查 Authorization 头
auth_header = request.headers.get("Authorization", "")
if auth_header != f"Bearer {API_KEY}":
return JSONResponse(
status_code=401,
content={"error": "未授权:缺少或无效的 API Key"}
)
return await call_next(request)
# ==================== 启动服务 ====================
# stdio 方式
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__":
import asyncio
asyncio.run(main())
# Streamable HTTP 方式
"""
if __name__ == "__main__":
import asyncio
async def main():
# 唯一的变化:用 streamable_http_server 替代 stdio_server
app = streamable_http_server(server)
# 加上鉴权中间件
app.add_middleware(AuthMiddleware)
# 用 uvicorn 启动 ASGI 服务
# ASGI (Asynchronous Server Gateway Interface) 是 Python Web 框架和 Web 服务器之间的一种异步通信标准
config = uvicorn.Config(app, host="0.0.0.0", port=8000, log_level="info")
http_server = uvicorn.Server(config)
await http_server.serve()
asyncio.run(main())
"""
# 启动服务 python server.py
Java
基于 JDK 17+ 以及 Spring AI 1.1.6 版本
- 引入依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
<version>1.1.6</version>
</dependency>
- 配置文件
spring:
ai:
mcp:
server:
annotation-scanner:
enabled: true # Enable/disable annotation scanning
name: bookmark-server
version: 1.0.0
protocol: stdio # 使用 stdio 传输
- 代码实现
package com.example.bookmarkserver;
import org.springframework.ai.mcp.server.McpServer;
import org.springframework.ai.mcp.server.McpTool;
import org.springframework.ai.mcp.server.McpResource;
import org.springframework.ai.mcp.server.McpToolParam;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
@SpringBootApplication
public class BookmarkServerApplication {
// ---------- 数据层 ----------
private final List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
SpringApplication.run(BookmarkServerApplication.class, args);
}
// ==================== 工具定义 ====================
@McpTool(name = "add_bookmark", description = "添加一个书签,需要提供标题和URL")
public String addBookmark(
@McpToolParam(description = "书签标题") String title,
@McpToolParam(description = "书签URL") String url) {
bookmarks.add(new Bookmark(title, url));
return "✅ 书签已添加: " + title + " (" + url + ")";
}
@McpTool(name = "search_bookmarks", description = "按关键词搜索书签")
public String searchBookmarks(
@McpToolParam(description = "搜索关键词") String keyword) {
String lowerKeyword = keyword.toLowerCase();
List<Bookmark> results = bookmarks.stream()
.filter(b -> b.title().toLowerCase().contains(lowerKeyword))
.toList();
if (results.isEmpty()) {
return "未找到匹配的书签。";
}
StringBuilder sb = new StringBuilder();
for (Bookmark b : results) {
sb.append("- ").append(b.title()).append(": ").append(b.url()).append("\n");
}
return sb.toString();
}
// ==================== 资源定义 ====================
@McpResource(uri = "bookmarks://list", name = "所有书签",
description = "返回所有已保存的书签列表", mimeType = "application/json")
public List<Bookmark> listBookmarks() {
return new ArrayList<>(bookmarks);
}
// ---------- 数据类 ----------
record Bookmark(String title, String url) {}
}
- 启动运行
mvn spring-boot:run
# 或者打包启动
java -jar bookmark-server.jar
MCP Client 客户端
无论哪种语言,MCP 客户端的调用流程都遵循相同的步骤:
| 步骤 | 操作 | 对应方法 (Python / Java) |
|---|---|---|
| 建立传输 | 通过 stdio 或 HTTP 连接 Server | stdio_client() / McpClient.Builder |
| 创建会话 | 基于传输创建 ClientSession | ClientSession() / McpSyncClient |
| 初始化握手 | 交换协议版本和能力信息 | session.initialize() / client.initialize() |
| 发现工具/资源 | 查询 Server 提供了什么 | list_tools() / client.listTools() |
| 调用工具 | 指定工具名和参数 | call_tool(name, args) / client.callTool() |
| 读取资源 | 指定资源 URI | read_resource(uri) / client.readResource() |
多 Server 聚合与命名空间
客户端连接多个 MCP Server 时,如何处理同名工具/资源冲突、如何路由
示例
使用 Python 和 Java(基于 Spring AI)作为客户端实现
Python
# 安装依赖 pip install mcp
# client.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# 1. 定义 Server 启动参数(命令行方式启动 Python Server)
server_params = StdioServerParameters(
command="python",
args=["server.py"] # 我们上一轮写的 Server 文件路径
)
# 2. 建立 stdio 连接
async with stdio_client(server_params) as (read, write):
# 3. 创建客户端会话
async with ClientSession(read, write) as session:
# 4. 初始化握手(必须)
await session.initialize()
# ========== 5. 列出并查看工具 ==========
tools_result = await session.list_tools()
print("=== 可用工具 ===")
for tool in tools_result.tools:
print(f" - {tool.name}: {tool.description}")
# ========== 6. 调用工具:添加书签 ==========
print("\n=== 调用 add_bookmark ===")
result = await session.call_tool(
"add_bookmark",
arguments={"title": "OpenAI 官网", "url": "https://openai.com"}
)
print(f"结果: {result.content[0].text}")
# 再添加一条
result = await session.call_tool(
"add_bookmark",
arguments={"title": "MCP 官方文档", "url": "https://modelcontextprotocol.io"}
)
print(f"结果: {result.content[0].text}")
# ========== 7. 调用工具:搜索书签 ==========
print("\n=== 调用 search_bookmarks ===")
result = await session.call_tool(
"search_bookmarks",
arguments={"keyword": "MCP"}
)
print(f"搜索结果:\n{result.content[0].text}")
# ========== 8. 读取资源 ==========
print("\n=== 读取资源 bookmarks://list ===")
resource_result = await session.read_resource("bookmarks://list")
# 资源内容在 contents[0].text 中(JSON 字符串)
print(f"所有书签:\n{resource_result.contents[0].text}")
if __name__ == "__main__":
asyncio.run(main())
# 启动客户端 python client.py
# 输出:
'''
=== 可用工具 ===
- add_bookmark: 添加一个书签,需要提供标题和URL
- search_bookmarks: 按关键词搜索书签
=== 调用 add_bookmark ===
结果: ✅ 书签已添加: OpenAI 官网 (https://openai.com)
结果: ✅ 书签已添加: MCP 官方文档 (https://modelcontextprotocol.io)
=== 调用 search_bookmarks ===
搜索结果:
- MCP 官方文档: https://modelcontextprotocol.io
=== 读取资源 bookmarks://list ===
所有书签:
[{"title": "OpenAI 官网", "url": "https://openai.com"}, {"title": "MCP 官方文档", "url": "https://modelcontextprotocol.io"}]
'''
Java
基于 JDK 17+ 以及 Spring AI 1.1.6 版本
- 依赖依赖
<dependencies>
<!-- Spring AI MCP Client 起步依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.1.6</version>
</dependency>
<!-- Spring Boot Web(为了方便演示,用 Web 方式触发调用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 配置文件
spring:
ai:
mcp:
client:
stdio:
connections:
bookmark-server:
command: java
args:
- -jar
- bookmark-server.jar
env:
API_KEY: SK-ASDFAAKSJDFKAJ
SPEC_DESC: 特殊描述
- 客户端实现
package com.example.mcpclient;
import org.springframework.ai.mcp.client.McpClient;
import org.springframework.ai.mcp.client.McpSyncClient;
import org.springframework.ai.mcp.spec.McpSchema;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@SpringBootApplication
public class McpClientDemoApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientDemoApplication.class, args);
}
// 注入 MCP 同步客户端
@Bean
public McpSyncClient mcpSyncClient(McpClient.Builder builder) {
return builder.connectionName("bookmark-server").build().sync();
}
// 注入多个 Server
@Bean
public McpSyncClient mcpSyncClient(McpClient.Builder builder) {
return builder.connectionName("bookmark-server").build().sync();
}
}
// ==================== 演示 Controller ====================
@RestController
class McpDemoController {
private final McpSyncClient client;
public McpDemoController(McpSyncClient client) {
this.client = client;
// 初始化握手(Spring AI 会自动处理,但确保连接已建立)
client.initialize();
}
@GetMapping("/demo")
public String runDemo() {
StringBuilder sb = new StringBuilder();
// 1. 列出工具
sb.append("=== 可用工具 ===\n");
var tools = client.listTools();
tools.tools().forEach(t ->
sb.append(" - ").append(t.name()).append(": ").append(t.description()).append("\n")
);
// 2. 添加书签
sb.append("\n=== 调用 add_bookmark ===\n");
var addResult1 = client.callTool(new McpSchema.CallToolRequest(
"add_bookmark",
Map.of("title", "Spring 官网", "url", "https://spring.io")
));
sb.append("结果: ").append(addResult1.content().get(0).text()).append("\n");
var addResult2 = client.callTool(new McpSchema.CallToolRequest(
"add_bookmark",
Map.of("title", "Spring AI 文档", "url", "https://docs.spring.io/spring-ai/reference/")
));
sb.append("结果: ").append(addResult2.content().get(0).text()).append("\n");
// 3. 搜索书签
sb.append("\n=== 调用 search_bookmarks ===\n");
var searchResult = client.callTool(new McpSchema.CallToolRequest(
"search_bookmarks",
Map.of("keyword", "Spring")
));
sb.append("搜索结果:\n").append(searchResult.content().get(0).text()).append("\n");
// 4. 读取资源
sb.append("\n=== 读取资源 ===\n");
var resourceResult = client.readResource(new McpSchema.ReadResourceRequest("bookmarks://list"));
sb.append("所有书签:\n").append(resourceResult.contents().get(0).text());
return "<pre>" + sb + "</pre>";
}
}
- 启动运行
mvn spring-boot:run
- 访问接口
http://localhost:8080/demo
完整配置
spring:
ai:
mcp:
client:
# ========== 全局设置 ==========
enabled: true # 启用 MCP 客户端
name: my-mcp-client # 客户端名称(用于日志和标识)
version: 1.0.0 # 客户端版本
type: SYNC # 客户端类型:SYNC(同步)或 ASYNC(异步)
request-timeout: 30s # 全局请求超时时间
# ========== SSE 连接配置(HTTP 远程 Server) ==========
sse:
connections:
# --- 连接 1:我们之前写的书签 Server(带 API Key 鉴权) ---
bookmark-server:
url: http://localhost:8000 # Server 的 HTTP 地址
sse-endpoint: /sse # SSE 端点路径(可选,默认 /sse)
# --- 连接 2:远程 Trunk MCP(带 OAuth) ---
trunk:
url: https://mcp.trunk.io/mcp
# --- 连接 3:不带鉴权的本地开发 Server ---
local-dev-server:
url: http://localhost:9000
# ========== Stdio 连接配置(本地进程) ==========
stdio:
connections:
# --- 连接 4:本地文件系统 Server ---
filesystem-server:
command: npx # 启动命令
args: # 启动参数
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "/Users/username/Desktop"
# --- 连接 5:本地 Python Server ---
python-server:
command: python
args:
- "/home/user/mcp-servers/my_server.py"
- "--port=8080"
Spring AI 也支持类似 Claude Code 的外部资源文件引入方式:
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.json
错误处理
- 传输层错误:连接中断、超时、重试、重连
- 协议层错误:JSON-RPC 标准错误码、自定义数据
- 应用层错误:应用逻辑错误、参数校验、部分失败
- 响应成功,返回一个带
isError=true标记的内容文本 - 直接抛出异常,升级为 JSON-RPC 的内部错误
- 响应成功,返回一个带
集成扩展
Claude Code
用户级配置(全局)
Claude Code 用户级(全局)配置基本都在用户主目录的 .claude.json 文件中
标准格式:
"mcpServers": {
"McpServerName": {
"command": "启动命令",
"args": ["参数1", "参数2"],
"type": "stdio"
}
}
配置方式
- 使用命令行快速添加:
claude mcp add --transport http trunk https://mcp.trunk.io/mcp --scope user
- 手动编辑文件:
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop"],
"type": "stdio", // 可选
"env": { // 可选
"API_KEY": "your-key",
"DEBUG": "true"
}
},
"trunk": {
"url": "https://mcp.trunk.io/mcp",
"type": "http", // 可选
"headers": {
"Authorization": "Bearer ${TRUNK_API_TOKEN}",
"X-API-Key": "your-key"
}
}
}
项目级配置
在项目的根目录创建 .mcp.json 文件:
{
"mcpServers": {
"trunk": {
"url": "https://mcp.trunk.io/mcp",
"type": "http"
}
}
}
也可以通过命令快速添加:
claude mcp add --transport http trunk https://mcp.trunk.io/mcp --scope project
管理命令
# 查看已安装的 MCP Server
claude mcp list
# 测试 MCP Server 连接
claude mcp get <名称>
# 删除 MCP Server
claude mcp remove <名称>
# 在交互模式中管理(推荐)
/mcp
认证
Claude Code 支持 OAuth 浏览器授权流程,连接后输出 /mcp 即可自动打开浏览器进行认证授权,也支持 API Token 认证,在配置中加入 header 字段即可:
{
"mcpServers": {
"trunk": {
"url": "https://mcp.trunk.io/mcp",
"type": "http",
"headers": {
"Authorization": "Bearer ${TRUNK_API_TOKEN}"
}
}
}
}
VSCode
使用命令面板
- 按
Cmd+Shift+P(Mac) 或Ctrl+Shift+P(Windows/Linux) 打开命令面板 - 输入并选择
MCP: add server - 根据提示依次选择传输类型(HTTP 或 Stdio)、填写 URL 或启动命令,以及服务器标识
- 选择配置生效范围:用户设置(所有项目通用)或工作区设置(仅当前项目)
如果连接的是 GitHub MCP Server 这类在线服务,VS Code 会自动弹出浏览器窗口引导你完成 OAuth 授权
手动编写配置文件
在项目根目录创建 .vscode/mcp.json 文件:
{
"servers": {
// 示例1:通过 Docker 启动本地 Server (Stdio)
"github-local": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github_token}"
}
},
// 示例2:连接远程 Server (HTTP)
"trunk": {
"url": "https://mcp.trunk.io/mcp",
"type": "http"
}
}
}
注意:VS Code 使用 MCP 时必须切换到 Agent 模式
高阶用法
将 VS Code 编辑器本身的功能(如代码检查、调试、符号搜索等)暴露成 MCP Server,让 其它 AI 工具(Claude Code/Codex/Cursr 等)来调用
这种方式通常需要先安装对应扩展,再在 AI 工具的配置文件里配置连接,例如想让 Claude 控制 VS Code 调试程序,需要在 Claude 的 .claude.json 里添加这类配置:
"mcpServers": {
"vscode": {
"command": "npx",
"args": ["github:malvex/mcp-server-vscode"]
}
}
MCP Server 推荐
- Github MCP:仓库管理
- Context 7:实时库文档查询
- Playwright:浏览器自动截图和测试
- PostgreSQL:数据库查询和分析
- Sentry:错误监控和日志分析
- OpenSpec:SDD 开发范式
- Google DevTools:谷歌浏览器开发工具
MCP/A2A (Agent-to-Agent) 对比分析
- Google 提出的"智能体到智能体"协议,与 MCP 对比:一个管"调工具",一个管"调智能体"
MCP/Function Calling/Tool Calling 对比分析
- MCP:通用开放协议
- Function Calling:模型内嵌能力,单次,无状态
- Tool Calling:模型内嵌能力,并行调用和多步推理,Function Calling 的扩展和抽象升级