package xyz.wbsite.mcp.basic; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Collections; import java.util.List; import java.util.Map; /** * 后控制器类 * 用于处理MCP POST请求 * * @author wangbing */ @RestController @RequestMapping("/mcp/post") public class PostController { private static final Logger log = LoggerFactory.getLogger(PostController.class); private static final String WEATHER_TOOL_NAME = "getWeatherForecast"; private static final String FAKE_WEATHER_JSON = "{\"forecast\": \"sunny\"}"; @Resource SseBroadcaster broadcaster; @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity handleMcpPostRequest(@RequestBody McpRequest request) { log.info("Received MCP POST Request: ID={}, Method={}", request.getId(), request.getMethod()); McpResponse mcpResponse = processRequest(request); if (mcpResponse != null) { // Send the response back over the SSE channel broadcaster.broadcastResponse(mcpResponse); } else { // 处理可能不会生成McpResponse对象的通知等情况 // 在我们的例子中,'通知/初始化'落在这里。 log.debug("No explicit response object generated for method '{}', assuming notification ack.", request.getMethod()); } // 立即返回HTTP 200 OK或202 Accepted以确认收到POST。 // 实际结果通过SSE异步发送。 // 200 OK可能更简单,因为客户希望得到一些响应体,即使是空的。 // 202 Accepted明确表示处理正在其他地方进行。让我们用200。 return ResponseEntity.ok().build(); } private McpResponse processRequest(McpRequest request) { switch (request.getMethod()) { case "initialize": log.info("Handling initialize request"); InitializeResult initResult = new InitializeResult(new ServerCapabilities()); return new McpResponse(request.getId(), initResult); case "notifications/initialized": log.info("Received initialized notification"); // 这是来自客户端的通知。MCP规范称通知 // 没有回应。所以我们在这里返回null,POST处理程序 // 将只返回HTTP OK。 return null; case "tools/list": log.info("Handling tools/list request"); ToolSpecificationData weatherTool = new ToolSpecificationData( WEATHER_TOOL_NAME, "Gets the current weather forecast.", new InputSchema( "object", Map.of("location", Map.of( "type", "string", "description", "Location to get the weather for") ), List.of("location"), false) ); ListToolsResult listResult = new ListToolsResult(List.of(weatherTool)); return new McpResponse(request.getId(), listResult); case "tools/call": log.info("Handling tools/call request"); if (request.getParams() != null && request.getParams().has("name")) { String toolName = request.getParams().get("name").asText(); if (WEATHER_TOOL_NAME.equals(toolName)) { log.info("Executing tool: {}", toolName); TextContentData textContent = new TextContentData(FAKE_WEATHER_JSON); CallToolResult callResult = new CallToolResult(List.of(textContent)); return new McpResponse(request.getId(), callResult); } else { log.warn("Unknown tool requested: {}", toolName); return new McpResponse(request.getId(), new McpError(-32601, "Method not found: " + toolName)); } } else { log.error("Invalid tools/call request: Missing 'name' in params"); return new McpResponse(request.getId(), new McpError(-32602, "Invalid params for tools/call")); } case "ping": log.info("Handling ping request"); return new McpResponse(request.getId(), Collections.emptyMap()); default: log.warn("Unsupported MCP method: {}", request.getMethod()); return new McpResponse(request.getId(), new McpError(-32601, "Method not found: " + request.getMethod())); } } }