Last active
June 25, 2026 11:19
-
-
Save senocak/5284714adebc1db4294c61c9778b28e9 to your computer and use it in GitHub Desktop.
OpenApi3Configuration for Spring AI McpServer Swagger UI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Configuration | |
| public class OpenApi3Configuration { | |
| @Bean | |
| public OpenApiCustomizer mcpToolCustomizer(final ListableBeanFactory beanFactory) { | |
| return openApi -> { | |
| final var sessionHeader = new io.swagger.v3.oas.models.parameters.Parameter() | |
| .in(SecurityScheme.In.HEADER.toString()) | |
| .name("mcp-session-id") | |
| .required(true) | |
| .allowEmptyValue(false) | |
| .description("MCP Session ID used for request tracking") | |
| .schema(new StringSchema()._default("00000000-0000-0000-0000-000000000000")); | |
| final var acceptHeader = new io.swagger.v3.oas.models.parameters.Parameter() | |
| .in("header") | |
| .name("Accept") | |
| .required(true) | |
| .explode(false) | |
| .allowEmptyValue(false) | |
| .description("Accepted response types: application/json, text/event-stream") | |
| .schema(new StringSchema()._default("application/json, text/event-stream")); | |
| final Tag mcpTag = new Tag() | |
| .name("MCP") | |
| .description("Model Context Protocol (JSON-RPC 2.0)"); | |
| final List<Example> examples = new ArrayList<>(); | |
| // initialize method | |
| final Map<String, Object> initialize = Map.of( | |
| "jsonrpc", "2.0", | |
| "id", 0, | |
| "method", "initialize", | |
| "params", Map.of( | |
| "protocolVersion", "2024-11-05", | |
| "clientInfo", Map.of( | |
| "name", "http-client", | |
| "version", "1.0.0" | |
| ), | |
| "capabilities", Map.of() | |
| ) | |
| ); | |
| examples.add(new Example() | |
| .summary("initialize") | |
| .value(initialize)); | |
| // tools/list method | |
| final Map<String, Object> toolsList = Map.of( | |
| "jsonrpc", "2.0", | |
| "id", 3, | |
| "method", "tools/list", | |
| "params", Map.of("_meta", Map.of("progressToken", 3)) | |
| ); | |
| examples.add(new Example() | |
| .summary("tools/list") | |
| .value(toolsList)); | |
| // tools/call methods | |
| final String[] beanNames = beanFactory.getBeanDefinitionNames(); | |
| int idCounter = 10; | |
| for (String beanName : beanNames) { | |
| Object bean; | |
| try { | |
| bean = beanFactory.getBean(beanName); | |
| } catch (Exception _) { | |
| continue; | |
| } | |
| final Class<?> targetClass = AopUtils.getTargetClass(bean); | |
| int finalIdCounter = idCounter; | |
| int finalIdCounter1 = idCounter; | |
| ReflectionUtils.doWithMethods(targetClass, method -> { | |
| final McpTool annotation = AnnotationUtils.findAnnotation(method, McpTool.class); | |
| if (annotation == null) | |
| return; | |
| final Map<String, Object> arguments = new LinkedHashMap<>(); | |
| for (Parameter parameter : method.getParameters()) { | |
| arguments.put(parameter.getName(), parameter.getType().getSimpleName()); | |
| } | |
| final Map<String, Object> toolCall = Map.of( | |
| "jsonrpc", "2.0", | |
| "id", finalIdCounter, | |
| "method", "tools/call", | |
| "params", Map.of( | |
| "name", annotation.name(), | |
| "arguments", arguments, | |
| "_meta", Map.of("progressToken", finalIdCounter1) | |
| ) | |
| ); | |
| examples.add(new Example() | |
| .summary(annotation.name()) | |
| .value(toolCall)); | |
| }); | |
| idCounter++; | |
| } | |
| // BUILD SWAGGER CONTENT (ONE ENDPOINT ONLY) | |
| final Map<String, Example> exampleMap = examples.stream() | |
| .collect(Collectors.toMap( | |
| Example::getSummary, | |
| e -> e, | |
| (a, _) -> a, | |
| LinkedHashMap::new | |
| )); | |
| final MediaType mediaType = new MediaType().examples(exampleMap); | |
| final RequestBody requestBody = new RequestBody() | |
| .required(true) | |
| .content(new Content().addMediaType("application/json", mediaType)); | |
| final ApiResponses responses = new ApiResponses(); | |
| final ApiResponse response = new ApiResponse() | |
| .description("JSON-RPC response or SSE stream") | |
| .content(new Content().addMediaType("application/json, text/event-stream", mediaType)); | |
| responses.addApiResponse("200", response); | |
| final Operation operation = new Operation() | |
| .summary("MCP JSON-RPC Endpoint") | |
| .description(""" | |
| Single MCP endpoint supporting JSON-RPC 2.0: | |
| - initialize | |
| - tools/list | |
| - tools/call | |
| Each request must include: | |
| - jsonrpc: "2.0" | |
| - id: number | |
| - method: string | |
| - params: object | |
| """) | |
| .tags(List.of(mcpTag.getName())) | |
| .requestBody(requestBody) | |
| .addParametersItem(sessionHeader) | |
| .addParametersItem(acceptHeader) | |
| .responses(responses); | |
| openApi.path("/mcp", new PathItem().post(operation)); | |
| openApi.addTagsItem(mcpTag); | |
| }; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment