mcp - 服务端根据权限分组输出 tools
2026年3月22日 上午10:44:04
从 mcp-sdk(java) 的角度,是没法实现动态输出 tools 的。mcp-sdk 的机制是先注册 tool,然后内部处理 "tools/list" 响应(而且特别内部)。要实现动态输出 tools ,一般有两种方案
- 修改 mcp-sdk 代码,扩展 "tools/list" 处理,由外部代理(不利于 mcp-sdk 的未来维护与升级)
- 先定义好 tools 组,然后在 http 接入处进行切换(这个是目前可以做的)
下面使用 http 接入处进行切换的方案,进行演示(v3.9.6 后支持)
1、容器风格
以下以 solon 容器为例。其它容器需要自己转下。
import io.modelcontextprotocol.server.transport.WebRxStatelessServerTransport;
import org.noear.solon.Solon;
import org.noear.solon.ai.annotation.PromptMapping;
import org.noear.solon.ai.annotation.ResourceMapping;
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.ai.chat.message.ChatMessage;
import org.noear.solon.ai.chat.prompt.MethodPromptProvider;
import org.noear.solon.ai.chat.resource.MethodResourceProvider;
import org.noear.solon.ai.chat.tool.MethodToolProvider;
import org.noear.solon.ai.mcp.McpChannel;
import org.noear.solon.ai.mcp.server.McpServerEndpointProvider;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Param;
import org.noear.solon.core.handle.Context;
import java.util.Arrays;
import java.util.Collection;
public class App {
public static void main(String[] args) {
Solon.start(App.class, args);
WebRxStatelessServerTransport serverTransport1 = buildMcpServerTransport("group1");
WebRxStatelessServerTransport serverTransport2 = buildMcpServerTransport("group2");
//接入时进行路由分发(不同的 ServerTransport,需要代理不同的方法。可以看它的 toHttpHandler 方法里有哪些处理)
Solon.app().router().get("/mcp", ctx -> {
int role = ctx.paramAsInt("role");
if (role == 1) {
serverTransport1.handleGet(ctx);
} else {
serverTransport2.handleGet(ctx);
}
});
Solon.app().router().post("/mcp", ctx -> {
int role = ctx.paramAsInt("role");
if (role == 1) {
serverTransport1.handlePost(ctx);
} else {
serverTransport2.handlePost(ctx);
}
});
}
public static WebRxStatelessServerTransport buildMcpServerTransport(String group) {
Object mcpBean = Solon.context().getBean(group);
if (mcpBean == null) {
return null;
}
McpServerEndpointProvider endpointProvider = McpServerEndpointProvider.builder()
.name(group)
.channel(McpChannel.STREAMABLE_STATELESS)
.sseEndpoint("/mcp")
.build();
//不同的 group ,用不同的 tools
endpointProvider.addTool(new MethodToolProvider(mcpBean));
endpointProvider.addResource(new MethodResourceProvider(mcpBean));
endpointProvider.addPrompt(new MethodPromptProvider(mcpBean));
//这里用 build(不用 start,否则会自动注册到路由器)
//(WebRxStatelessServerTransport) 要与 .channel(McpChannel.STREAMABLE_STATELESS) 对应起来
return (WebRxStatelessServerTransport) endpointProvider.getServer().build();
}
@Component("group1")
public static class McpServerTool {
//@Inject //容器形态可以注入(否则,要自己组件需要的服务)
//UserService userService;
@ToolMapping(description = "查询天气预报")
public String getWeather(@Param(description = "城市位置") String location, Context ctx) {
return "晴,14度";
}
@ResourceMapping(uri = "config://app-version", description = "获取应用版本号", mimeType = "text/config")
public String getAppVersion() {
return "v3.2.0";
}
@PromptMapping(description = "生成关于某个主题的提问")
public Collection<ChatMessage> askQuestion(@Param(description = "主题") String topic) {
return Arrays.asList(
ChatMessage.ofUser("请解释一下'" + topic + "'的概念?")
);
}
}
}
2、Java 原生风格
import io.modelcontextprotocol.server.transport.WebRxStatelessServerTransport;
import org.noear.solon.Solon;
import org.noear.solon.ai.annotation.PromptMapping;
import org.noear.solon.ai.annotation.ResourceMapping;
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.ai.chat.message.ChatMessage;
import org.noear.solon.ai.chat.prompt.MethodPromptProvider;
import org.noear.solon.ai.chat.resource.MethodResourceProvider;
import org.noear.solon.ai.chat.tool.MethodToolProvider;
import org.noear.solon.ai.mcp.McpChannel;
import org.noear.solon.ai.mcp.server.McpServerEndpointProvider;
import org.noear.solon.annotation.Param;
import org.noear.solon.core.handle.Context;
import java.util.Arrays;
import java.util.Collection;
public class App {
public static void main(String[] args) {
Solon.start(App.class, args);
WebRxStatelessServerTransport serverTransport1 = buildMcpServerTransport("group1");
WebRxStatelessServerTransport serverTransport2 = buildMcpServerTransport("group2");
//接入时进行路由分发(不同的 ServerTransport,需要代理不同的方法。可以看它的 toHttpHandler 方法里有哪些处理)
Solon.app().router().get("/mcp", ctx -> {
int role = ctx.paramAsInt("role");
if (role == 1) {
serverTransport1.handleGet(ctx);
} else {
serverTransport2.handleGet(ctx);
}
});
Solon.app().router().post("/mcp", ctx -> {
int role = ctx.paramAsInt("role");
if (role == 1) {
serverTransport1.handlePost(ctx);
} else {
serverTransport2.handlePost(ctx);
}
});
}
private static WebRxStatelessServerTransport buildMcpServerTransport(String group) {
McpServerEndpointProvider endpointProvider = McpServerEndpointProvider.builder()
.name(group)
.channel(McpChannel.STREAMABLE_STATELESS)
.sseEndpoint("/mcp")
.build();
//不同的 group ,用不同的 tools
endpointProvider.addTool(new MethodToolProvider(new McpServerTool()));
endpointProvider.addResource(new MethodResourceProvider(new McpServerTool()));
endpointProvider.addPrompt(new MethodPromptProvider(new McpServerTool()));
//这里用 build(不用 start,否则会自动注册到路由器)
//(WebRxStatelessServerTransport) 要与 .channel(McpChannel.STREAMABLE_STATELESS) 对应起来
return (WebRxStatelessServerTransport) endpointProvider.getServer().build();
}
private static class McpServerTool {
@ToolMapping(description = "查询天气预报")
public String getWeather(@Param(description = "城市位置") String location, Context ctx) {
return "晴,14度";
}
@ResourceMapping(uri = "config://app-version", description = "获取应用版本号", mimeType = "text/config")
public String getAppVersion() {
return "v3.2.0";
}
@PromptMapping(description = "生成关于某个主题的提问")
public Collection<ChatMessage> askQuestion(@Param(description = "主题") String topic) {
return Arrays.asList(
ChatMessage.ofUser("请解释一下'" + topic + "'的概念?")
);
}
}
}