MCP 技术详解

6 阅读20分钟

核心概念

MCP - Model Context Protocol - 模型上下文协议

核心是定义了一套统一的接口,类似于 USB 接口,只要支持 MCP 协议,就可以接入

MCP 协议可概括为:以 JSON-RPC 2.0 为通信基础,通过能力协商来提供 资源(Resources)、工具(Tools)、提示(Prompts) 三大原语的标准化协议

基础协议(Base Protocol)

所有客户端和服务端之间的消息都必须遵循 JSON-RPC 2.0 规范,并定义了三种消息类型:

  • 请求(Requests):发起操作,必须包含唯一的 idmethod,对方会回送一个携带相同 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:必填,特殊情况除外,由客户端/服务端生成的唯一标识符,可以是字符串、数字或 null
  • method:必填,要调用的远程方法名
  • params:可选,调用方法的参数,可以是位置区分的数组,或以名称区分的对象

响应对象(Response)

客户端/服务端收到请求后,必须返回一个响应对象,且只包含 resulterror 中的一个

成功响应

  • jsonrpc2.0
  • id:必填,与发起请求的 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 请求
  • 服务端反向 - 进度通知 (服务端 → 客户端 / 有去无回无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 可以由多条消息组成,每条消息都有明确的 rolecontent,其中 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 连接 Serverstdio_client() / McpClient.Builder
创建会话基于传输创建 ClientSessionClientSession() / McpSyncClient
初始化握手交换协议版本和能力信息session.initialize() / client.initialize()
发现工具/资源查询 Server 提供了什么list_tools() / client.listTools()
调用工具指定工具名和参数call_tool(name, args) / client.callTool()
读取资源指定资源 URIread_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 官网(1.1.6 版本)

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 的扩展和抽象升级

参考