Skip to content

MCP: Model Context Protocol

本文介绍MCP(Model Context Protocol)的基本概念,如何在Cursor中使用以及如何使用Spring AI实现MCP Server。

1. MCP介绍

1.1 概述

MCP,全称Model Context Protocol,译为模型上下文协议,是用于连接LLM和外部系统的开源协议。

mcp

MCP为LLM扩展了以下能力:

  • 工具:MCP为LLM提供了额外的工具,LLM可以根据请求和上下文,调用MCP提供的工具;
  • 资源:MCP为LLM提供了额外的资源(例如内部文档、数据库信息等),LLM可以根据上下文,获取这些资源;

1.2 架构

1.2.1 MCP架构介绍

在MCP架构中,存在以下三个角色:

  • MCP Host:指的是AI应用程序(例如Codex、Cursor等),用于协调、管理MCP Client;
  • MCP Client(客户端):用户连接MCP Server 的组件,用于从MCP Server中获取工具/资源共MCP Host使用;
  • MCP Server(服务端):为MCP Client提供工具/资源的代码程序;

mcp architexture

服务端分为本地服务端和远程服务端:

  • 本地服务端:与MCP Host运行中在同一台机器上的服务端程序,与MCP Host通过STDIO交换数据;
  • 远程服务端:与MCP Host运行在不同机器上的服务端程序,与MCP Host通过HTTP协议交换数据;

在MCP Client和MCP Server连接中,涉及两层架构:

  • 传输层(Transport Layer):定义支持客户端和服务器之间数据交换的通信机制和通道,包括特定于传输的连接建立、消息成帧和授权;
  • 数据层(Data Layer):定义基于 JSON-RPC 的客户端-服务器通信协议,包括生命周期管理和核心原语,例如工具、资源、提示和通知;

也就是说,传输层不关心数据的内容,只关心连接、发送和接收数据;数据层定义了双方对话的“语言”和“语法”。

1.2.2 传输层

传输层定义了数据如何在客户端(Client)和服务端(Server)之间物理移动。它不关心数据的内容,只关心连接、发送和接收数据。

目前 MCP 主要支持两种传输方式:

  • STDIO (标准输入输出):
    • 场景: MCP Server在本地运行;
    • 机制: Client 启动 Server 进程,通过 stdin 发送 JSON,通过 stdout 读取 JSON;
    • 特点: 极简,无需网络配置,安全性高(仅限本地);
  • HTTP + SSE:
    • 场景: MCP Server远程运行或云端服务;
    • 机制: Client 发送 POST 请求,Server 通过 SSE 流式返回;
    • 特点: 支持跨机器通信、身份验证(OAuth);

SSE是Server-Sent Event的缩写,SSE 建立在标准的 HTTP 协议之上。它的工作原理非常像“长流水”:

  1. 客户端发送一个普通的 HTTP 请求;
  2. 服务端不关闭连接,而是保持连接开启;
  3. 服务端以 text/event-stream 的格式,源源不断地发送数据块;

1.2.3 数据层

数据层使用基于 JSON RPC 2.0 的数据交换格式,定义了数据的语法结构与语义,数据层包括以下内容:

  • 生命周期管理:用于管理在客户端和服务端之间的连接,包括连接建立、能力协商、连接关闭;
  • 服务端功能:使服务端能够提供核心功能,包括 AI 操作工具、上下文数据资源以及与客户端交互模板的提示;
  • 客户端功能:允许服务端向客户端发送请求,包括获取用户输入、从LLM中采样、将日志消息发送到客户端;
  • 其他功能:包括实时更新消息通知、长程任务进度跟踪;

关于JSON RPC:是一种轻量级的远程过程调用(Remote Procedure Call, RPC)协议,基于 JSON(JavaScript Object Notation)作为数据格式,常用于客户端与服务端之间的通信,尤其在 Web、区块链、后端服务之间非常常见。

JSON RPC的核心思想:客户端发送一个 JSON 请求,调用服务端的某个“方法”,服务端返回执行结果。

JSON RPC规定了三种基本的消息类型:

  • Requests (请求): 有 ID,要求对方必须回复(如:call_tool);
  • Notifications (通知): 无 ID,发完即忘(如:进度更新、资源变更通知);
  • Responses (响应): 对应请求的执行结果;

更多关于JSON RPC的文档,参考资料[2]

1.2.4 服务端

MCP Server (以下简称服务端)是一段代码,用于向LLM提供特定的能力,包括工具、资源、Prompts(提示词模板)。

工具(Tools)

工具(Tools)允许LLM执行操作,每个工具定义了一个特定的操作(例如写入数据库、修改文件、调用API等),LLM可以根据上下文调用工具。

对于工具,LLM 拥有“自主调用权”,也就是说,当连接了一个包含“工具”的 MCP Server 后,不需要用户手动点击按钮去运行工具,LLM 会在分析问题后,自发决定是否需要使用某个工具。

关于工具,服务端需要定义两个方法:

方法作用返回
tools/list列出所有的工具工具定义列表
tools/call调用指定的工具工具执行结果

例如,当调用tools/list后返回工具列表:

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}
json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "calculator_arithmetic",
        "title": "Calculator",
        "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
        "inputSchema": {
          "type": "object",
          "properties": {
            "expression": {
              "type": "string",
              "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
            }
          },
          "required": ["expression"]
        }
      },
      {
        "name": "weather_current",
        "title": "Weather Information",
        "description": "Get current weather information for any location worldwide",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "City name, address, or coordinates (latitude,longitude)"
            },
            "units": {
              "type": "string",
              "enum": ["metric", "imperial", "kelvin"],
              "description": "Temperature units to use in response",
              "default": "metric"
            }
          },
          "required": ["location"]
        }
      }
    ]
  }
}

当调用tools/call用于执行工具:

json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "weather_current",
    "arguments": {
      "location": "San Francisco",
      "units": "imperial"
    }
  }
}
json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
      }
    ]
  }
}

资源(resources)

资源(resources)提供了额外的信息,可以帮助LLM更全面的理解问题。

注意,资源的加载是由MCP Host基于规则或用户选择来控制的,也就是说,当连接上提供资源的MCP Server后,并不是由LLM直接访问资源,而是由MCP Host(也就是AI应用程序)获取相关资源,然后提供给LLM。因此,从大量外部资源中,选择与当前问题最相关的资源提供给LLM,是MCP Host的责任。

在MCP中,每个资源都有唯一的标识符,并且声明了数据格式(MIME Type),有了这两者,就可以解决如何发现资源以及如何解析资源的问题。

MCP把资源分为两类:

  • Direct Resources(静态资源):URI 是固定的,不带参数;例如calendar://events/2024,用于获取2024 年的所有日历事件;
  • Resource Templates(动态资源):URI 是模板,支持参数占位符(如 {city});例如travel://activities/{city}/{category},用于获取Barcelona 的 museums 类型活动;

关于资源,MCP Server定义了以下方法:

方法作用返回
resources/list获取所有静态资源静态资源列表
resources/templates/list获取所有动态资源动态资源列表
resources/read获取资源内容带元数据的资源内容
resources/subscribe订阅资源变动订阅结果

Prompts(提示词模板) Prompts 在 MCP 中是“用户触发的任务模板 + 上下文构造器”。它把零散的自然语言需求转为结构化参数,并在服务端编排 Resources(取数)/Tools(能力),生成标准化的 messages(system/user 等),从而为 LLM 提供稳定、可复用的高质量输入,提升结果一致性与可控性。

对于Prompts,必须是由用户显式触发。

对于Prompts,MCP Server提供两个方法:

方法作用返回
prompts/list列出所有的提示词模板提示词模板列表
prompts/get获取某个具体的提示词内容完整的提示词

举一个例子说明在MCP中,Prompts(提示词模板)的作用

假设用户直接输入“帮我规划一个去巴塞罗那的旅行”给LLM,此时LLM规划的结果可能不准确。

而用户通过直接调用提示词模板,此时会要求输入关键信息,然后,在MCP Server内部,可能会获取相关信息/执行特定工作,然后返回经过精心设计的提示词,例如:

Details
typescript
server.handle("prompts/get", async ({ name, arguments: args }) => {
  if (name === "plan-vacation") {
    
    // 1️⃣ 调用 Resource(获取城市信息)
    const cityInfo = await server.callResource(
      `travel://city/${args.destination}`
    );

    // 2️⃣ 调用 Resource Template(获取活动)
    const activities = await server.callResource(
      `travel://activities/${args.destination}`
    );

    // 3️⃣ 组合 Prompt
    return {
      messages: [
        {
          role: "system",
          content: "You are a professional travel planner"
        },
        {
          role: "user",
          content: `
Plan a ${args.duration}-day trip to ${args.destination}.

City info:
${cityInfo}

Activities:
${activities}

Budget: ${args.budget}
Interests: ${args.interests}
`
        }
      ]
    };
  }
});

此时再将MCP Server返回的完整提示词发送给LLM,之后LLM再进行规划,就会更准确。

简单概括Prompts用法

  1. AI 程序通过 prompts/list 发现可用模板;
  2. 用户选择模板并填写参数(有类型、必填校验与补全);
  3. 调用 prompts/get
  4. MCP Server 解析参数→可选调用资源/工具→拼装 messages 返回;
  5. AI 程序 将 messages 发送给 LLM 执行;

1.2.5 客户端

除了MCP 服务端可以提供功能,在MCP设计中,客户端也可以提供功能,主要有以下三个功能:

  • Roots(本地文件范围):告诉 Server:“你可以访问哪些本地文件目录”,形式:file:// 路径;
  • Sampling(让 Server 请求 LLM):Server 可以“反过来请求 Client 调用 LLM”,Client 控制模型调用(成本/权限在 Client);
  • Elicitation(反向向用户提问):Server 可以请求 Client,“问用户一个问题”或“让用户补充信息”;

由于客户端能力目前使用率较低,不过多介绍。

1.3 工作流程

本小节简单概述一下MCP的工作流程,主要分为以下步骤:启动、发现、交互

  1. 连接与初始化

    MCP Host 启动: 用户打开AI程序(即MCP Host),Host 读取配置文件(例如 mcp_config.json);

    创建 Server 进程: AI程序根据配置启动 Server 进程(本地 STDIO)或建立网络连接(远程 HTTP/SSE);

    建立连接: AI程序为每个Server初始化Client组件,Client 发起 initialize 请求,双方交换版本信息和各自支持的功能(Capabilities,例如是否支持 Sampling 或 Roots);

  2. 能力发现

    扫描资源与工具: Client 向 Server 请求可用列表:

    • list_resources: 看看有哪些只读数据;
    • list_tools: 看看有哪些可执行的函数;
    • list_prompts: 看看有哪些预设的指令模板;
  3. 交互

    用户提问: 用户说:“分析一下本地项目里的UserService.java代码”;

    Host 调度: MCP Host 识别到用户意图涉及特定资源,主动通过 Client 发送 resources/read 请求给Local MCP Server,获取代码内容;

    上下文注入: MCP Host 将获取的资源内容合并到用户的提问中,一起发给 LLM;

    LLM 决策: LLM 判断需要读取数据库,发出指令:“tool_call”;

    **Host 调度:**MCP Host接受到LLM返回的工具调用响应,找到对应的Remote MCP Server提供的工具(也有可能是其他工具)并执行,并且将执行结果返回给LLM;

2. 在Cursor中使用MCP

本小节介绍如何在Cursor中使用MCP。

2.1 配置MCP Server

首先需要在Cursor中添加MCP Servers,即在mcp.json中添加,例如:

  • 添加本地MCP Server,并且是以npx命令启动:

    json
    {
      "mcpServers": {
        "server-name": {
          "command": "npx",
          "args": ["-y", "mcp-server"],
          "env": {
            "API_KEY": "value"
          }
        }
      }
    }
  • 添加本地MCP Server,并且是以python命令启动:

    json
    {
      "mcpServers": {
        "server-name": {
          "command": "python",
          "args": ["mcp-server.py"],
          "env": {
            "API_KEY": "value"
          }
        }
      }
    }
  • 添加远程MCP Server:

    json
    // MCP server using HTTP or SSE - runs on a server
    {
      "mcpServers": {
        "server-name": {
          "url": "http://localhost:3000/mcp",
          "headers": {
            "API_KEY": "value"
          }
        }
      }
    }

根据mcp.json的位置不同,MCP Server所生效的范围也不同:

  • 当前目录下的.cursor/mcp.json:项目范围;
  • ~/.cursor/mcp.json:用户目录下,全局范围;

对于本地MCP Server,可以配置以下参数:

参数是否必须描述示例
typeYes连接类型"stdio"
commandYes用于启动本地MCP Server的命令(在本地环境可用)"npx", "node", "python", "docker"
argsNo启动参数["server.py", "--port", "3000"]
envNo环境变量{"API_KEY": "${env:api-key}"}
envFileNo环境变量文件".env", "${workspaceFolder}/.env"

在MCP Server配置中,command, args, env, url,和 headers参数值,可以使用占位符,Cursor提供了以下占位符:

  • ${env:NAME} :环境变量;
  • ${userHome} :用户主目录;
  • ${workspaceFolder} :工作区目录 (也就是包含 .cursor/mcp.json 的目录);
  • ${workspaceFolderBasename} :工作区名称;
  • ${pathSeparator} and ${/} :操作系统路径分隔符;

例如:

json
{
  "mcpServers": {
    "local-server": {
      "command": "python",
      "args": ["${workspaceFolder}/tools/mcp_server.py"],
      "env": {
        "API_KEY": "${env:API_KEY}"
      }
    }
  }
}

例如,在Cursor中配置postgres MCP Server:

json
{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://localhost/demo"
      ]
    }
  }
}

然后在Cursor Setting -> Tools & MCPs中,就能看到安装的MCP Servers了,并且每个MCP Server有哪些工具、资源、Prompt都会显示:

image-20260502225132450

2.2 使用MCP Server

当我们在对话框中向LLM发出请求时,LLM会利用安装的MCP工具,进行查询:

image-20260502225331652

默认情况下,执行MCP提供的工具,需要人工确认。

3. 自定义MCP

3.1 STDIO MCP Server

在本小节中介绍如何利用 Spring AI 实现自己的MCP Server,关于在本地运行的天气服务。

TIP

如果要实现基于STDIO的MCP Server,不要在代码中使用System.out.println()System.out.print(),因为这些方法会直接写入标准输出,会扰乱JSON RPC信息。

首先在pom.xml中引入以下依赖:

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
    <version>2.0.0-M5</version>
</dependency>

然后修改添加如下配置:

properties
spring.ai.mcp.server.stdio=true
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
  • spring.ai.mcp.server.stdio=true:显式开启 MCP 的 stdio 传输模式
  • spring.main.web-application-type=none:禁用 Web 容器,在 stdio 模式下,不需要监听任何端口;
  • spring.main.banner-mode=off:彻底关闭 Spring 启动时的字符横幅,因为Spring启动时字符横幅时输出到标准输出的,所以在STDIO模式下会对MCP通讯有影响;
  • logging.pattern.console=:清空控制台日志的输出,同样也是避免输出到标准输出,对MCP有影响;

然后创建工具:

java
@Service
public class WeatherService {

    public WeatherService() {

    }

    @Tool(description = "Get weather forecast for a specific latitude/longitude")
    public String getWeatherForecastByLocation(
            double latitude,   // Latitude coordinate
            double longitude   // Longitude coordinate
    ) {
        return "多云";
    }

    @Tool(description = "Get weather alerts for a US state")
    public String getAlerts(
            @ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
    ) {
        return "暴雨预警";
    }
}
  • @Tool:定义了工具;

注册工具:

java
@SpringBootApplication
public class McpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }

    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return  MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
    }

}

之后,运行mvn clear install命令,在target目录下找到打包好的jar包,然后在Cursor中配置:

json
{
  "mcpServers": {
    "weather": {
      "command": "java",
      "args": [
        "-jar",
        "/absolute-path/mcp-server/target/mcp-server-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

image-20260503113005307

在对话框中试验如下:

image-20260503113109148

3.2 SSE MCP Server

要将STDIO的MCP Server换成SSE MCP Server,首先将依赖换成如下依赖:

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
    <version>2.0.0-M5</version>
</dependency>

然后删除以下关于STDIO的配置:

properties
#spring.ai.mcp.server.stdio=true
#spring.main.web-application-type=none
#spring.main.banner-mode=off
#logging.pattern.console=

这样就转换完成了,直接启动项目:

image-20260503113403095

在Cursor中配置如下:

json
{
    "weather": {
      "url": "http://localhost:8080/sse"
    }
  }
}

即可正常使用。

参考资料

[1] MCP:https://modelcontextprotocol.io/docs/getting-started/intro

[2] JSON RPC: https://www.jsonrpc.org/specification

[3] Cursor MCP:https://cursor.com/docs/mcp

[4] Spring AI MCP:https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html