大模型开发 - Spring AI 之 @McpTool、@McpPrompt、@McpResource

大模型开发 - Spring AI 之 @McpTool、@McpPrompt、@McpResource

文章目录

在这里插入图片描述
本文深入讲解 Model Context Protocol (MCP) 的三大核心能力——Tool(工具)、Prompt(提示词模板)和 Resource(资源)。通过 Spring AI 的 McpSyncClient,我们可以在客户端优雅地调用 MCP 服务器暴露的这些能力,实现更灵活的 AI 应用架构。

引言

在前一篇文章中,我们学习了如何使用 @McpTool 注解在 MCP 服务器中定义工具,并通过 Spring AI 的工具调用机制进行远程工具调用。但这仅仅是 MCP 协议的冰山一角。

MCP 是一个更广泛的协议生态,不仅支持工具调用,还支持:

  1. Tool — 可调用的函数或方法,执行具体业务逻辑
  2. Prompt — 预定义的提示词模板,支持参数化,用于快速生成高质量的初始 prompt
  3. Resource — MCP 服务器管理的资源(配置、数据、文件等),客户端可以读取

本文的主人公是第 23 次提交,它在 McpController 中新增了两个端点 /mcpPrompt/mcpResource,演示了如何在客户端直接调用 MCP 服务器的 Prompt 和 Resource 能力,而不仅仅依赖于自动的工具调用。

MCP 协议的三大能力

让我们先建立一个整体认识:

能力对比表

能力用途定义位置调用方式典型场景
Tool执行具体操作或计算MCP 服务器(@McpTool)自动工具调用或 callTool()获取天气、搜索资讯、文件操作
Prompt提供预制的 prompt 模板MCP 服务器(@McpPrompt)listPrompts() 列表,getPrompt() 获取欢迎语、工作流模板、角色 prompt
Resource访问服务器管理的资源MCP 服务器(@McpResource)readResource() 读取配置参数、知识库、系统状态

三者的关系与数据流

┌─────────────────────────────────────────────┐ │ Spring AI 客户端应用 │ │ (McpController 使用 McpSyncClient) │ └───────────────┬─────────────────────────────┘ │ │ MCP 协议 │ (JSON-RPC over stdio/TCP) ▼ ┌─────────────────────────────────────────────┐ │ MCP 服务器 (WeatherService) │ ├─────────────────────────────────────────────┤ │ @McpTool: getWeather(cityName) │ │ @McpPrompt: greeting(name) │ │ @McpResource: getConfig(key) │ └─────────────────────────────────────────────┘ 
  • Tool 在大模型决策时被主动调用,参与推理过程
  • Prompt 是静态或半静态的文本模板库,供客户端查询与使用
  • Resource 是服务器数据的接口,客户端可按需读取

Prompt 能力深解

什么是 Prompt?

在 MCP 中,Prompt 是一种可参数化的文本模板。不同于硬编码的系统 prompt,MCP Prompt 允许:

  1. 动态参数化 — 通过参数传入动态内容(如用户名、日期、数据)
  2. 版本管理 — 服务器可维护多个版本的 prompt 模板
  3. 跨应用共享 — 多个客户端应用可复用同一套 prompt 模板库

服务器端:@McpPrompt 定义

spring-ai-mcp-server-demo 中,我们在 WeatherService 定义了一个问候语 prompt:

@McpPrompt(name ="greeting", description ="欢迎语")publicMcpSchema.GetPromptResultgreeting(@McpArg(name ="name")String name){String message ="你好, "+ name +"! 有什么可以帮您?";returnnewMcpSchema.GetPromptResult("Greeting",List.of(newMcpSchema.PromptMessage(McpSchema.Role.ASSISTANT,newMcpSchema.TextContent(message))));}

关键点:

  • @McpPrompt — 标记该方法作为 MCP prompt 源
  • name = "greeting" — prompt 的唯一标识符
  • description = "欢迎语" — 对 prompt 用途的简短说明
  • @McpArg(name = "name") — 定义可参数化的参数
  • 返回值 GetPromptResult 包含一条或多条 PromptMessage,每条消息有角色(ASSISTANT/USER)和内容

客户端:通过 McpSyncClient 调用 Prompt

在客户端,我们通过 McpSyncClient列举和获取 prompt:

第一步:列举所有 Prompt

@GetMapping("/mcpPrompt")publicStringmcpPrompt(String question){McpSyncClient mcpSyncClient = mcpSyncClients.get(0);// 列举 MCP 服务器提供的所有 promptMcpSchema.ListPromptsResult listPromptsResult = mcpSyncClient.listPrompts();List<McpSchema.Prompt> prompts = listPromptsResult.prompts();// 获取第一个 prompt 的元数据McpSchema.Prompt prompt = prompts.get(0);return prompt.toString();}

此时得到的是 prompt 的元数据,而非其具体内容:

{"name":"greeting","description":"欢迎语","arguments":{"type":"object","properties":{"name":{"type":"string","description":"用户名"}}}}

第二步:获取具体的 Prompt 内容(含参数)

注释中展示的实现:

McpSchema.GetPromptRequest getPromptRequest =newMcpSchema.GetPromptRequest("greeting",Map.of("name","周瑜"),null);McpSchema.GetPromptResult result = mcpSyncClient.getPrompt(getPromptRequest);// 提取文本内容String promptText =((McpSchema.TextContent)result.messages().get(0).content()).text();// 输出:"你好, 周瑜! 有什么可以帮您?"

调用流程:

  1. 构造 GetPromptRequest,包含 prompt 名称(“greeting”)和参数映射({“name”: “周瑜”})
  2. 调用 mcpSyncClient.getPrompt(),MCP 协议将请求发往服务器
  3. 服务器的 greeting() 方法被执行,参数注入到方法
  4. 返回 GetPromptResult,包含消息列表
  5. 客户端通过类型转换提取文本内容

Prompt 的应用场景

场景 1:构建系统 Prompt 库

某个 AI 应用需要支持多个角色(产品经理、工程师、设计师),每个角色有不同的 system prompt。这些 prompt 不需要在代码中硬编码,而是由 MCP 服务器统一维护:

// 客户端String rolePrompt =getPromptFromMcp("role_"+ role,Map.of("domain", domain));ChatMessage systemMessage =newChatMessage(MessageType.SYSTEM, rolePrompt); chatClient.prompt().system(systemMessage).user(question).call().content();

场景 2:多语言 Prompt 管理

服务器维护英文、中文、日文等多语言的 prompt 版本,客户端根据 locale 参数动态获取:

String promptName ="greeting_"+ locale;// "greeting_zh", "greeting_en", etc.McpSchema.GetPromptResult result = mcpSyncClient.getPrompt(newMcpSchema.GetPromptRequest(promptName,Map.of("userName", user),null));

场景 3:工作流模板

复杂的多步骤 AI 工作流(如需求分析 → 方案设计 → 代码生成 → 测试)的每一步都有标准 prompt,服务器维护这些 prompt 的版本演进。

Resource 能力深解

什么是 Resource?

MCP Resource 是一个统一的资源访问接口。通过 URI 式的寻址,客户端可以读取服务器管理的任意资源:

  • 配置参数 — 应用配置、API 密钥、特性开关
  • 知识库 — 文档、FAQ、业务规则
  • 运行时数据 — 系统状态、用户信息、动态数据
  • 文件资源 — 模板文件、预训练模型、数据文件

服务器端:@McpResource 定义

WeatherService 中定义一个配置资源:

@McpResource(uri ="config://{key}", name ="configuration")publicStringgetConfig(String key){return environment.getProperty(key,"123");}

关键点:

  • @McpResource — 标记该方法暴露资源接口
  • uri = "config://{key}" — 资源的 URI 模式,其中 {key} 是通配符,会被注入到方法参数
  • name = "configuration" — 资源类型或分组的名称
  • 方法参数 key 来自 URI 中的 {key} 占位符

支持的资源类型

MCP 规范定义了多种资源内容类型:

// 文本资源McpSchema.TextResourceContents textResource =newMcpSchema.TextResourceContents(text);// 二进制资源McpSchema.BlobResourceContents blobResource =newMcpSchema.BlobResourceContents(base64EncodedData, mimeType);

客户端:通过 McpSyncClient 读取 Resource

完整的资源读取流程:

@GetMapping("/mcpResource")publicStringmcpResource(String question){McpSyncClient mcpSyncClient = mcpSyncClients.get(0);// 1. 构造资源读取请求// URI "config://username" 中,{key} 被替换为 "username"McpSchema.ReadResourceRequest readResourceRequest =newMcpSchema.ReadResourceRequest("config://username");// 2. 调用 mcpSyncClient 读取资源McpSchema.ReadResourceResult readResourceResult = mcpSyncClient.readResource(readResourceRequest);// 3. 从结果中提取内容// contents 是一个列表,通常只有一个元素McpSchema.ResourceContents resourceContents = readResourceResult.contents().get(0);// 4. 类型转换为 TextResourceContents,提取文本String configValue =((McpSchema.TextResourceContents)resourceContents).text();return configValue;// 若找不到,返回默认值 "123"}

执行流程分解:

客户端构造请求 ↓ 读取 URI "config://username" ↓ MCP 协议转发到服务器 ↓ 服务器的 getConfig("username") 被调用 ↓ 方法返回配置值(或默认值) ↓ 包装成 TextResourceContents ↓ 客户端接收并类型转换提取文本 

Resource 的应用场景

场景 1:集中配置管理

所有应用从 MCP 服务器读取配置,实现配置的动态修改而无需重启应用:

String apiKey =readResource("config://openai_api_key");String maxTokens =readResource("config://max_tokens");String enableCache =readResource("config://enable_cache");

场景 2:知识库快速查询

一个 FAQ 知识库由 MCP 服务器维护,不同的应用可以快速查询:

String faqContent =readResource("faq://how_to_register");String troubleshootingGuide =readResource("faq://common_issues");

场景 3:用户权限与配置

读取当前用户的权限配置、个性化设置等:

String userPermissions =readResource("user://current/permissions");String userTheme =readResource("user://current/preferences/theme");

场景 4:业务规则引擎

维护业务规则(定价规则、风控规则、营销规则等),应用通过 Resource 动态获取最新规则:

String pricingRule =readResource("rules://pricing/v2024Q1");String riskControlPolicy =readResource("rules://risk_control/current");

代码实现详解

完整的 McpController

packagecom.Artisan;importio.modelcontextprotocol.client.McpSyncClient;importio.modelcontextprotocol.spec.McpSchema;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.tool.ToolCallbackProvider;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.List;importjava.util.Map;@RestControllerpublicclassMcpController{@AutowiredprivateChatClient chatClient;@AutowiredprivateToolCallbackProvider toolCallbackProvider;@AutowiredprivateList<McpSyncClient> mcpSyncClients;/** * 使用自动工具调用机制 */@GetMapping("/mcp")publicStringmcp(String message){return chatClient .prompt().user(message).toolCallbacks(toolCallbackProvider.getToolCallbacks()).call().content();}/** * 手动调用 Prompt 能力 * 列举所有可用的 prompt,获取第一个 prompt 的元数据 */@GetMapping("/mcpPrompt")publicStringmcpPrompt(String question){McpSyncClient mcpSyncClient = mcpSyncClients.get(0);// 列举所有 promptMcpSchema.ListPromptsResult listPromptsResult = mcpSyncClient.listPrompts();List<McpSchema.Prompt> prompts = listPromptsResult.prompts();// 获取第一个 prompt 的元数据McpSchema.Prompt prompt = prompts.get(0);return prompt.toString();// 也可以获取具体 prompt 内容(含参数):// McpSchema.GetPromptRequest getPromptRequest =// new McpSchema.GetPromptRequest("greeting", Map.of("name", "周瑜"), null);// McpSchema.GetPromptResult promptResult = mcpSyncClient.getPrompt(getPromptRequest);// String promptText = ((McpSchema.TextContent)promptResult.messages().get(0).content()).text();// return promptText;}/** * 手动调用 Resource 能力 * 读取 MCP 服务器管理的配置资源 */@GetMapping("/mcpResource")publicStringmcpResource(String question){McpSyncClient mcpSyncClient = mcpSyncClients.get(0);// 构造资源读取请求McpSchema.ReadResourceRequest readResourceRequest =newMcpSchema.ReadResourceRequest("config://username");// 读取资源McpSchema.ReadResourceResult readResourceResult = mcpSyncClient.readResource(readResourceRequest);// 提取第一个资源内容McpSchema.ResourceContents resourceContents = readResourceResult.contents().get(0);// 类型转换为文本资源并提取文本return((McpSchema.TextResourceContents)resourceContents).text();}}

完整的 WeatherService(MCP 服务器端)

packagecom.Artisan;importio.modelcontextprotocol.spec.McpSchema;importlombok.extern.log4j.Log4j2;importorg.springaicommunity.mcp.annotation.McpArg;importorg.springaicommunity.mcp.annotation.McpPrompt;importorg.springaicommunity.mcp.annotation.McpResource;importorg.springaicommunity.mcp.annotation.McpTool;importorg.springframework.ai.tool.annotation.Tool;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.core.env.Environment;importorg.springframework.stereotype.Service;importjava.util.List;@Service@Log4j2publicclassWeatherService{@AutowiredprivateEnvironment environment;/** * Tool 能力:获取天气 */@McpTool(description ="获取指定城市的天气")publicStringgetWeather(String cityName){ log.info("正在获取天气信息...");if(cityName.equals("上海")){return"天晴";}elseif(cityName.equals("北京")){return"下雨";}return"不知道";}/** * Prompt 能力:欢迎语模板(支持参数化) */@McpPrompt(name ="greeting", description ="欢迎语")publicMcpSchema.GetPromptResultgreeting(@McpArg(name ="name")String name){String message ="你好, "+ name +"! 有什么可以帮您?";returnnewMcpSchema.GetPromptResult("Greeting",List.of(newMcpSchema.PromptMessage(McpSchema.Role.ASSISTANT,newMcpSchema.TextContent(message))));}/** * Resource 能力:读取配置资源 * URI 模式:config://{key} * 例如:config://username 会调用 getConfig("username") */@McpResource(uri ="config://{key}", name ="configuration")publicStringgetConfig(String key){return environment.getProperty(key,"123");}}

类型转换的细节

在客户端接收到 Resource 后,需要进行显式的类型转换。这是因为 ResourceContents 是一个接口,具体实现可能是:

// 文本资源if(resourceContents instanceofMcpSchema.TextResourceContents){String text =((McpSchema.TextResourceContents)resourceContents).text();}// 二进制资源if(resourceContents instanceofMcpSchema.BlobResourceContents){String mimeType =((McpSchema.BlobResourceContents)resourceContents).mimeType();String data =((McpSchema.BlobResourceContents)resourceContents).data();// Base64}

实际项目中,可以写一个工具方法来简化:

privateStringextractTextResource(McpSchema.ResourceContents resourceContents){if(resourceContents instanceofMcpSchema.TextResourceContents){return((McpSchema.TextResourceContents)resourceContents).text();}thrownewIllegalArgumentException("Expected TextResourceContents");}

  • 使用 @McpTool 定义工具
  • 通过 ToolCallbackProvider 获取所有工具的 callback
  • 通过 chatClient.toolCallbacks() 让大模型自动调用工具

这是 Tool 能力的完整体现 —— 大模型在推理过程中自主决策是否需要调用工具,框架自动处理调用和结果注入。

这里引入了两个新的 MCP 能力:

  1. Prompt 能力 — 不再仅依靠硬编码 system prompt,而是从 MCP 服务器动态获取参数化的 prompt 模板
  2. Resource 能力 — 超越工具调用的业务逻辑,直接访问服务器管理的资源和配置

这三个能力形成了 MCP 生态的完整三角

┌─────────────────────────────────────────┐ │ MCP 生态的三大支柱 │ ├─────────────────────────────────────────┤ │ Tool → 动态业务逻辑执行 │ │ Prompt → 动态文本模板获取 │ │ Resource → 动态资源与配置读取 │ └─────────────────────────────────────────┘ 

最佳实践与常见陷阱

最佳实践

1. 合理划分 Tool、Prompt、Resource 的职责

需求选择原因
执行计算或操作ToolTool 专门用于执行业务逻辑
提供文本模板PromptPrompt 为文本生成优化设计
读取静态或配置数据ResourceResource 用于数据和配置访问
修改数据或状态ToolResource 只读,修改需要 Tool

2. Prompt 参数化设计

不要硬编码参数值,始终使用参数化:

// 不推荐@McpPrompt(name ="greeting_john")publicMcpSchema.GetPromptResultgreetingJohn(){...}// 推荐@McpPrompt(name ="greeting")publicMcpSchema.GetPromptResultgreeting(@McpArg(name ="name")String name){...}

3. Resource URI 的一致性

定义明确的 URI 规范,保证客户端和服务器的一致性:

// 约定:config://部分/键名@McpResource(uri ="config://{module}/{key}")publicStringgetConfig(Stringmodule,String key){...}// 客户端调用readResource("config://database/host");readResource("config://cache/ttl");

4. 错误处理

MCP 调用可能失败,需要适当的异常处理:

try{McpSchema.ReadResourceResult result = mcpSyncClient.readResource(request);if(result.contents().isEmpty()){ logger.warn("Resource not found: {}", request.uri());return defaultValue;}return((McpSchema.TextResourceContents)result.contents().get(0)).text();}catch(Exception e){ logger.error("Failed to read resource", e);return defaultValue;}

常见陷阱

陷阱 1:忘记类型转换

// 错误:直接将 ResourceContents 当作 TextResourceContentsString text = resourceContents.text();// ❌ ResourceContents 无此方法// 正确:先进行类型转换String text =((McpSchema.TextResourceContents)resourceContents).text();// ✓

陷阱 2:忘记处理空列表

// 错误:假设 contents 总是非空McpSchema.ResourceContents contents = result.contents().get(0);// 正确:检查列表是否为空if(result.contents().isEmpty()){return defaultValue;}McpSchema.ResourceContents contents = result.contents().get(0);

陷阱 3:混淆 Prompt 名称与内容

// 错误:listPrompts() 返回的是元数据(名称、描述、参数定义),不是实际内容McpSchema.ListPromptsResult result = mcpSyncClient.listPrompts();String content = result.prompts().get(0).toString();// 这是元数据,不是实际 prompt 内容// 正确:使用 getPrompt() 获取实际内容McpSchema.GetPromptRequest request =newMcpSchema.GetPromptRequest("greeting", params,null);McpSchema.GetPromptResult result = mcpSyncClient.getPrompt(request);String content =((McpSchema.TextContent)result.messages().get(0).content()).text();

总结

本文深入讲解了 MCP 协议的三大能力在 Spring AI 中的实现:

核心要点

  1. Tool 能力 — 执行业务逻辑,通过 @McpTool 定义,大模型自动调用
  2. Prompt 能力 — 提供参数化的文本模板,通过 @McpPrompt 定义,客户端动态查询与填充
  3. Resource 能力 — 提供资源与配置的统一访问界面,通过 @McpResource 定义,客户端按 URI 读取

客户端调用模式

// Tool:自动调用(第 22 讲) chatClient.toolCallbacks(callbacks).call().content();// Prompt:手动列举与获取 listPromptsResult = mcpSyncClient.listPrompts(); getPromptResult = mcpSyncClient.getPrompt(request);// Resource:手动读取 readResourceResult = mcpSyncClient.readResource(request);

设计思想

MCP 的设计体现了关注点分离的原则:

  • Tool 关注 能力 —— 服务器提供什么操作
  • Prompt 关注 模板 —— 服务器维护什么知识
  • Resource 关注 数据 —— 服务器管理什么资源

通过 MCP 这个统一协议,我们可以构建更灵活、更可扩展的 AI 应用架构 —— 业务逻辑、提示词知识和配置数据可以独立演进,通过协议契约进行集成。

这正是 Spring AI 对"企业级 AI 应用"的理解:不是将一切硬编码到代码中,而是通过清晰的协议和抽象,实现服务与客户端的解耦,让不同团队可以独立迭代。

在这里插入图片描述

Read more

嵌入式软件代码架构详解,超清晰图解为什么需要软件架构,以及告诉你怎么实现软件架构

我希望你能够带着几个问题进入到下面的文章中,我会用生动的例子告诉你为什么需要软件架构,以及一个简单的软件架构是什么样子的。在看文章的过程中,你要有意识的思考这几个问题,希望看完这篇文章,你就能回答出下面几个问题了。 1.为什么需要软件架构? 2.好的软件架构有哪些标准,能够解决掉什么问题? 3.软件架构长什么样子?文章看完了你能够画出来嘛? 一、 告别“面条代码”,嵌入式为何更需要软件架构? 1.1 从两个场景说起 当你拿到一块新的开发板,兴致勃勃地开始你的嵌入式项目时,是不是经常这样开始你的main.c? 场景A(新手期):功能堆砌的“面条代码” 这就是经典的“面条代码”(Spaghetti Code)——所有逻辑像一碗面条一样缠绕在一起。 它有些什么样的问题呢? * main函数无限膨胀: 所有功能都堆在while(1)循环里,代码越来越长,越来越难阅读。 * “牵一发而动全身”: 你想修改按键的逻辑,可能会影响到ADC采样;想移除蜂鸣器功能,得小心翼翼地从一大坨代码里找出所有相关行。 * 高度耦合: 业务逻辑(按键控制LED)

By Ne0inhk
金仓数据库 MongoDB 兼容:多模融合下的架构之道与实战体验

金仓数据库 MongoDB 兼容:多模融合下的架构之道与实战体验

引言:从“平替”到“超越”的技术跨越 在国产化替代(信创)浪潮下,选择数据库不再只是考量“能否使用”,更多关注其“好用与否”,还要看是否能做到“无缝切换”。提到 MongoDB,想必大家都不生疏,作为 NoSQL 领域的佼佼者,凭借自身灵活的数据架构和飞快的读写效率,斩获诸多互联网及物联网项目,不过须要诚实地表明,一旦关乎到企业核心业务,譬如要确保数据完全一致,执行繁杂的关联查询或者实施统一运作管理时,MongoDB 就常常会有些力不从心。 电科金仓(Kingbase)所给出的多模融合数据库方案颇具趣味,该方案并非仅仅创建一层适配层来博取眼球,其实在架构层面上执行了“降维打击”,经由内核级别的 MongoDB 协议适配 并结合自主研发的 OSON 存储引擎,金仓把“关系型数据库稳定的基础”与“NoSQL 灵活的特性”融合起来,现在,让我们一起探究金仓数据库(KingbaseES,

By Ne0inhk
Tomcat下载安装以及配置(详细教程)

Tomcat下载安装以及配置(详细教程)

本文讲的是Java环境 文章目录 * 前言 * 下载及安装Tomcat * 启动Tomcat * 测试Tomcat * 配置Tomcat 环境变量 * IDEA中配置Tomcat * Eclipse中配置Tomcat 前言 提示:这里可以添加本文要记录的大概内容: 今天晚上查看自己原来项目的时候,突然发现运行不了,仔细查看发现是tomcat没配置,但是tomcat在电脑里已经下载过了,只是还没有配置,这篇文章就讲tomcat在电脑与idea中的配置 提示:以下是本篇文章正文内容,下面案例可供参考 下载及安装Tomcat 进入tomcat官网,Tomcat官网 选择需要下载的版本,点击下载 下载路径一定要记住,并且路径中尽量不要有中文 下载后是压缩包 .zip,解压后 tomcat系统各个文件夹目录是什么意义: bin:放置的是Tomcat一些相关的命令,启动的命令(startup)和关闭的命令(shutdown)等等 conf:(configure)配置文件 lib:(library)库,依赖的 jar包 logs:Tomca

By Ne0inhk
OpenClaw 配置 Nginx 反向代理完整指南

OpenClaw 配置 Nginx 反向代理完整指南

OpenClaw 配置 Nginx 反向代理完整指南 将 OpenClaw Gateway 安全地暴露到公网,并通过 HTTPS 和登录保护确保访问安全。 前言 OpenClaw 是一个强大的 AI 助手网关,默认情况下它只监听本地回环地址 (127.0.0.1:18789)。如果你想从外部网络访问 Control UI,或者为团队提供安全的访问入口,配置 Nginx 反向代理是最佳实践。 本文将介绍如何: * ✅ 配置 Nginx 反向代理到 OpenClaw * ✅ 启用 HTTPS (Let’s Encrypt SSL 证书) * ✅ 添加 Basic Auth 登录保护 * ✅ 配置 OpenClaw 信任代理模式 环境准备 * 服务器:

By Ne0inhk