Skip to content

在 Feat Cloud 中暴露 MCP 服务

This content is not available in your language yet.

MCP 这页最容易写成“大量注解参数说明”,但那样其实帮不了你真正落地。
更实际的问题是:如果你已经有一个 Feat Cloud 应用,现在想把它变成一个 MCP 服务,应该从哪种方式开始。

先判断:你需要哪一种 MCP 暴露方式

Section titled “先判断:你需要哪一种 MCP 暴露方式”

在 Feat Cloud 里,MCP 大体有两种用法:

当你的 Controller 里出现 @Tool@Prompt@Resource 这些注解时,Feat Cloud 可以在编译期帮你生成默认 MCP 服务。

适合:

  • 你只是想把少量工具或资源先暴露出去
  • 你不打算自定义一整套 MCP 路径
  • 你希望先用最少的配置打通能力

如果你需要明确指定 MCP 服务名、SSE 端点、消息端点和流式端点,就用 @McpEndpoint

适合:

  • 你已经把 MCP 当成一个明确的服务能力来设计
  • 你希望自己控制端点路径和服务边界
  • 你不想完全依赖默认端点行为

如果你只是想先把一个工具暴露给 MCP 客户端,不一定需要自定义服务端点。

下面这种 Controller 已经足够表达一个最小用例:

DefaultMcpController.java
import tech.smartboot.feat.cloud.annotation.Controller;
import tech.smartboot.feat.cloud.annotation.mcp.Tool;
import tech.smartboot.feat.cloud.annotation.mcp.Param;
@Controller
public class DefaultMcpController {
@Tool(description = "简单的问候工具")
public String greeting(@Param(required = true, description = "用户姓名") String name) {
return "你好," + name + "";
}
}

这类写法的重点不是“看起来像注解示例”,而是它已经足够让编译期生成 MCP 对应服务能力。

一旦你开始真正管理一个 MCP 服务,而不是单独抛几个工具,就应该考虑 @McpEndpoint

仓库里的真实示例是:

McpDemoController.java
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.Prompt;
import tech.smartboot.feat.cloud.annotation.mcp.Resource;
import tech.smartboot.feat.cloud.annotation.mcp.Tool;
import tech.smartboot.feat.cloud.annotation.mcp.Param;
@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 UserInfo getUserInfo(
@Param(required = true, description = "用户ID") Long userId,
@Param(required = false, description = "是否包含详细信息") Boolean detailed
) {
UserInfo user = new UserInfo();
user.setId(userId);
user.setName("User " + userId);
user.setEmail(userId + "@example.com");
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 可调用工具。
如果你的目标是“让模型调用这段业务逻辑”,从这里开始最自然。

把一段模板化提示词暴露出去。
更适合“把一套提示结构交给外部 MCP 客户端使用”,而不是普通 Controller 里直接返回文本。

暴露一段文本资源或二进制资源。
适合团队规范、说明文档、静态内容、图片等可被外部读取的材料。

描述 MCP 工具或提示词参数。
这里的 @Param 不是普通 Controller 查询参数的同一层概念,而是 MCP 参数元信息的一部分。

如果你是第一次做 MCP,不要一上来就把 Tool、Prompt、Resource 全部堆在一个大 Controller 里。
更稳的做法通常是:

  1. 先暴露一个最小 Tool
  2. 再加一个 Prompt
  3. 最后再考虑是否需要 Resource

也就是说,先确认“服务能被 MCP 客户端发现和调用”,再扩能力。

仓库里也有直接注入 McpServer 的例子,例如 McpToolMainController
这类写法更适合你在代码里动态管理工具,而不是完全依赖注解生成。

如果你当前只是想把业务能力暴露出去,优先用注解方式。
当你开始需要更精细的控制时,再考虑直接操作 McpServer

我已经加了注解,但外部看不到 MCP 能力

Section titled “我已经加了注解,但外部看不到 MCP 能力”

优先检查:

  1. 当前类是否真的被 Feat Cloud 扫描到
  2. 你是否走的是默认 MCP 服务,还是自定义端点
  3. 相关编译期生成代码是否真的已经产生

@Tool 和普通 Controller 方法是什么关系

Section titled “@Tool 和普通 Controller 方法是什么关系”

它们不是二选一的关系。
一个类可以既是普通 Controller,又暴露 MCP 能力,但这时你最好明确它的职责边界,不要把所有事情混在一个超大类里。

当你的目标不只是“做一个 HTTP 接口”,而是“让模型或 MCP 客户端以结构化方式理解并调用我的应用能力”时,MCP 才真正有意义。