MCP 能力暴露
前面的章节一直在讲 HTTP:Controller 接收请求,调用业务代码,再返回文本或 JSON。MCP 让同一类业务能力多了一个入口:模型客户端可以发现它、理解它的参数,并把它当成工具、提示模板或资源来调用。
Feat Cloud 的优势仍然来自编译期生成。你在 Controller 上声明 @Tool、@Prompt、@Resource 或 @McpEndpoint,编译期会把这些声明转成 MCP Server 的注册代码。运行时要做的事情很少:加载生成类,把能力挂到对应端点上。

先把一个方法变成工具
Section titled “先把一个方法变成工具”如果只是把少量业务方法开放给 MCP 客户端,不需要一开始就设计完整端点。给方法加 @Tool,给参数补上 @Param 元信息,就能先跑通最小链路:
import tech.smartboot.feat.cloud.annotation.Controller;import tech.smartboot.feat.cloud.annotation.mcp.Tool;import tech.smartboot.feat.cloud.annotation.mcp.Param;
@Controllerpublic class DefaultMcpController {
@Tool(description = "简单的问候工具") public String greeting(@Param(required = true, description = "用户姓名") String name) { return "你好," + name + "!"; }}这里的 @Param 不是 HTTP 查询参数,而是 MCP 参数描述。它告诉客户端这个工具需要什么输入、参数是否必填、参数含义是什么。对模型客户端来说,这些描述会直接影响工具能不能被正确选择和调用。
设计 MCP 端点
Section titled “设计 MCP 端点”Feat Cloud 提供两种 MCP 暴露方式:默认 MCP 服务和显式 @McpEndpoint。
默认服务还是独立端点
Section titled “默认服务还是独立端点”默认 MCP 服务适合早期接入:项目里出现 @Tool、@Prompt、@Resource 后,Feat Cloud 会生成默认 MCP Server,把这些能力挂进去。它的价值是低成本验证:先确认客户端能发现工具、能发起调用、能拿到结果。
当 MCP 已经成为应用边界的一部分,就应该使用 @McpEndpoint 显式定义端点。它让服务名、SSE 路径、消息路径和 Streamable HTTP 路径都写在代码里,部署、网关和客户端配置也更容易保持稳定。
import tech.smartboot.feat.ai.mcp.enums.PromptType;import tech.smartboot.feat.ai.mcp.model.ToolResult;import tech.smartboot.feat.cloud.annotation.Controller;import tech.smartboot.feat.cloud.annotation.mcp.McpEndpoint;import tech.smartboot.feat.cloud.annotation.mcp.Param;import tech.smartboot.feat.cloud.annotation.mcp.Prompt;import tech.smartboot.feat.cloud.annotation.mcp.Resource;import tech.smartboot.feat.cloud.annotation.mcp.Tool;
import java.util.HashMap;import java.util.Map;
@Controller@McpEndpoint( name = "demo-mcp-service", title = "Demo MCP Service", sseEndpoint = "/mcp/demo/sse", sseMessageEndpoint = "/mcp/demo/sse/message", streamableEndpoint = "/mcp/demo/stream")public class McpDemoController {
@Tool(name = "getUserInfo", description = "根据用户ID获取用户信息") public Map<String, Object> getUserInfo( @Param(required = true, description = "用户ID") Long userId, @Param(required = false, description = "是否包含详细信息") Boolean detailed ) { Map<String, Object> user = new HashMap<>(); user.put("id", userId); user.put("name", "User " + userId); user.put("email", userId + "@example.com"); user.put("detailed", Boolean.TRUE.equals(detailed)); return user; }
@Prompt( name = "codeReviewPrompt", description = "代码审查提示词", type = PromptType.TEXT ) public String codeReviewPrompt( @Param(required = true, description = "编程语言") String language, @Param(required = true, description = "代码片段") String codeSnippet ) { return String.format("请审查以下%s代码并提供改进建议:%s", language, codeSnippet); }
@Resource( uri = "/resources/coding-standards.md", name = "编码规范", description = "团队编码规范文档", mimeType = "text/markdown", isText = true ) public String codingStandards() { return "# 编码规范"; }
@Tool(description = "获取文本信息") public ToolResult.TextContent getTextInfo() { return ToolResult.ofText("这是文本内容"); }}这段代码里有三类能力:
@Tool暴露可调用能力@Prompt暴露提示模板@Resource暴露文本或二进制资源
@McpEndpoint 则负责把这组能力放进一个明确的 MCP 服务边界里。到了这个阶段,你应该像设计 HTTP API 一样设计 MCP 端点:路径要稳定,名称要能表达服务意图,参数描述要足够让调用方理解。
Tool、Prompt、Resource 的分工
Section titled “Tool、Prompt、Resource 的分工”@Tool 适合执行动作或查询数据。如果你的目标是“让模型调用这段业务逻辑”,从 Tool 开始最自然,例如查用户、算价格、生成报表摘要。
@Prompt 适合暴露提示模板。它不是普通 Controller 里直接返回一段文本,而是把一套可复用的提示结构交给 MCP 客户端,让客户端在合适时填入参数并使用。
@Resource 适合暴露稳定材料,例如团队规范、产品说明、接口文档、图片或二进制资源。资源一般不表达“做一件事”,而是让客户端读取一份内容。
不要把 MCP Controller 写成大杂烩
Section titled “不要把 MCP Controller 写成大杂烩”MCP 能力应该围绕调用方的任务组织,而不是围绕注解类型堆叠。一个面向代码审查的 MCP 服务,可以包含代码审查 Prompt、读取编码规范的 Resource、查询项目元数据的 Tool;另一个面向运维的 MCP 服务,则应该独立成另一组 Controller 和端点。
如果你是第一次接入 MCP,比较稳的顺序是:
- 先暴露一个最小 Tool,确认客户端能调用
- 再补充必要的参数描述,观察模型是否能正确选择工具
- 需要复用提示结构时再加入 Prompt
- 需要稳定材料时再加入 Resource
- 服务边界稳定后,再迁移到显式
@McpEndpoint
什么时候直接操作 McpServer
Section titled “什么时候直接操作 McpServer”注解方式适合大多数静态能力:工具、提示和资源在编译期就能确定,Feat Cloud 可以直接生成注册代码。
如果你的工具集合需要在运行时动态变化,例如从数据库、插件系统或外部配置加载工具,就可以注入 McpServer 并在代码里管理。这个方式更灵活,也意味着你要自己维护更多边界:何时注册、何时下线、如何避免重复、如何处理并发变化。
所以默认选择很简单:能用注解清楚表达的能力,优先交给编译期生成;只有当能力本身就是动态的,再直接操作 McpServer。