分享一个把你的API快速升级为MCP规范的方案,可在线体验

前言

前段时间,Anthropic发布了新版本MCP协议,在Remote MCP Server场景下实现了显著改进。此次更新的核心亮点是Streamable HTTP传输机制,它不仅继承了HTTP+SSE的实时消息传输能力,还通过一系列优化使得连接更加稳定、数据传输更具弹性。新版协议采用单一MCP端点同时支持HTTP POST和GET请求,通过强制使用Mcp-Session-Id头实现会话管理,并支持批量请求、响应和通知,以及SSE流的可恢复性。(摘自:开源 Remote MCP Server 一站式托管来啦)

作为java语言最流行的框架Spring,Spring AI模块中也接入了MCP,Spring AI MCP通过Spring Boot接入扩展了MCPJavaSDK,提供客户端和服务器启动器。 使用Spring初始化器引导您的AI应用程序支持MCP。(摘自:Spring AI官网)

今天给大家带来一个使用Spring AI MCP开发的开源项目:easy-remote-mcp,通过简单的实现思路,使用Spring AI MCP一个java应用支持多个MCP Server。为开发者提供一种高效且便捷的方式来管理和整合多个MCP服务器。(目前仅支持工具调用)

话不多说,直接上项目演示和体验地址。


项目演示

项目体验地址:http://123.56.187.33:8082/home (本地址仅供学习参考,未做任何安全防护,不要上传个人私密信息哦。)

核心功能:通过页面配置,把HTTP接口转化为标准MCP工具,并且提供MCP Client能直接访问的sse url。

1.首先进入首页,我们能看到已经创建好的两个示例的MCP Server,大家自己创建的MCP Server也会在这里展示。

因为该网址只为体验学习,不涉及用户登录,所以设置查看密钥来保护大家自己创建的MCP Server,可以在保存时自定义设置查看密钥。

2.进入新增/编辑页面 可以看到当前MCP Server的名称描述和下方提供的工具信息。其中请求url、请求方法、请求体就是需要转换为MCP工具的HTTP接口的信息。

目前仅支持GET和POST请求方式,GET真正请求时会将请求参数直接拼接在请求url上;POST请求则会转为json作为请求体,也就是仅支持application/json。

这里提供一个示例接口,内部是调用了高德地图的api。

点击查看sse访问url就可以看到当前MCP Server的访问url了。

3.进入cline使用我们的MCP Server,配置中新增我们的MCP Server配置。

在cline中使用我们的MCP Server。如果更新了我们的MCP Server,在cline中刷新也是能直接看到最新的信息的。


核心实现方式

Spring AI把McpServer作为Spring Bean进行了管理,所以Spring AI提供的MCP支持默认的就是一个java应用一个MCP Server。在这里我们对McpServer进行自主管理,做到一个java应用管理多个McpServer。

java 复制代码
// 新增McpServer逻辑处理

// 对于每个新创建的MCP Server,生成一个随机字符串作为唯一标识
String randomStr = RandomUtil.randomString(6);
// 为每个MCP Server创建一个单独的访问url,使用 /randomStr/sse 的格式确定访问url。
// 为这个新的MCP Server创建一个的协议,访问url添加刚刚生成的唯一标识字符串,确保每个MCP Server的唯一性
WebFluxSseServerTransportProvider transportProvider = new WebFluxSseServerTransportProvider(new ObjectMapper(), "", "/" + randomStr + "/sse/message", "/" + randomStr + "/sse");
McpSchema.Implementation serverInfo = new McpSchema.Implementation(mcpServer.getName(), "1.0.0");
io.modelcontextprotocol.server.McpServer.AsyncSpecification serverBuilder = io.modelcontextprotocol.server.McpServer.async(transportProvider).serverInfo(serverInfo);
// 工具转换,把参数转化为Spring AI提供的ToolCallback,这里定义自己的RemoteMcpToolCallback
List<RemoteMcpToolCallback> remoteMcpToolCallbacks = RemoteMcpUtil.convertToolCallback(detailVO.getTools());
// 工具转换,转换为符合MCP规范标准的工具类
List<McpServerFeatures.AsyncToolSpecification> asyncToolSpecification = toAsyncToolSpecification(remoteMcpToolCallbacks);
// 为新的MCP Server添加工具
serverBuilder.tools(asyncToolSpecification);
McpSchema.ServerCapabilities build = McpSchema.ServerCapabilities.builder().tools(true).build();
serverBuilder.capabilities(build);
McpAsyncServer asyncServer = serverBuilder.build();
// 添加到map集合中 以便后续对该MCP Server的更新和删除
serverMap.put(randomStr, asyncServer);
toolSpecificationMap.put(randomStr, asyncToolSpecification);
// 添加新的MCP Server路由
remoteMcpHandlerMapping.addMapping(randomStr, transportProvider.getRouterFunction());

每个MCP Server都有单独的路由url,自定义RemoteMcpHandlerMapping来实现各自的请求处理。

java 复制代码
@Component
public class RemoteMcpHandlerMapping extends AbstractHandlerMapping{

    // 每个MCP Server的路由映射
    private final Map<String, RouterFunction<?>> mappings = new HashMap<>();

    // DispatcherHandler会调用此方法获取处理当前请求的handler
    @Override
    protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
        // 获取路径中的随机字符串 MCP Server的唯一标识
        String randomStr = exchange.getRequest().getURI().getPath().split("/")[1];
        if (StrUtil.isBlank(randomStr) || !mappings.containsKey(randomStr)) {
            return Mono.empty();
        }
        ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
        // 使用特定的MCP Server的请求处理handler
        return mappings.get(randomStr).route(request).doOnNext((handler) -> this.setAttributes(exchange.getAttributes(), request, handler));
    }
    
    // path:随机字符串,每个MCP Server的唯一标识
    // mapping 路由
    public void addMapping(String path, RouterFunction<?> mapping) {
        mappings.put(path, mapping);
    }


    public void clear(String path) {
        mappings.remove(path);
    }
}

自定义ToolCallback,用于处理工具调用的逻辑。

java 复制代码
public class RemoteMcpToolCallback implements ToolCallback {

    private ToolDefinition toolDefinition;

    private ToolMetadata toolMetadata;

    private ToolVO toolVO;

    @Override
    public ToolDefinition getToolDefinition() {
        return toolDefinition;
    }

    @Override
    public ToolMetadata getToolMetadata() {
        return toolMetadata;
    }
    
    // 这里是简单的直接使用http工具类进行http请求,直接返回请求结果
    @Override
    public String call(String toolInput) {
        JSONObject entries = JSONUtil.parseObj(toolInput);
        // 如果是GET请求,把参数拼在url上
        if (StrUtil.equals(HttpMethod.GET.name(), toolVO.getMethod())) {
            StringBuilder url = new StringBuilder(toolVO.getUrl());
            if (!entries.isEmpty()) {
                url.append("?");
                for (Map.Entry entry : entries.entrySet()) {
                    url.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
                }
                url = new StringBuilder(url.substring(0, url.length() - 1));
            }
            return HttpUtil.get(url.toString());
        } else {
            return HttpUtil.post(toolVO.getUrl(), entries.toString());
        }
    }

    @Override
    public String call(String toolInput, ToolContext tooContext) {
        return call(toolInput);
    }
}

最后

目前该项目仅完成了初版,已支持核心功能,但在业务逻辑处理和代码上还有很多不完善的地方,后续会新增oauth2验证等;如果存在代码逻辑错误等严重问题,还请多多指正,一起学习共同成长。

我相信,AI Agent结合MCP的未来一定是Local MCP Server与Remote MCP Server相辅相成。

相关推荐
chendilincd6 分钟前
C++ 的史诗级进化:从C++98到C++20
java·c++·c++20
独行soc19 分钟前
2025年渗透测试面试题总结-拷打题库08(题目+回答)
java·linux·运维·服务器·python·面试·职场和发展
~欸嘿28 分钟前
pdf多文件合并
java·pdf
IT可乐29 分钟前
人人都可以做个满血版的Manus智能体了
后端
艾文伯特32 分钟前
Maven集成模块打包&使用
java·maven
像风一样自由202039 分钟前
RESTful API工具和框架详解
后端·restful
草捏子41 分钟前
接口幂等性设计:6种解决方法让重复请求不再成为系统隐患
后端
Captaincc41 分钟前
AI coding的隐藏王者,悄悄融了2亿美金
前端·后端·ai编程
GoMaxAi1 小时前
开源Midjourney替代方案:企业级AI绘画+PPT生成系统+AI源码
人工智能·ai作画·开源·自动化·aigc·powerpoint·midjourney
盖世英雄酱581361 小时前
同事说缓存都用redis啊,数据不会丢失!真的吗?
redis·后端·面试