11package io .kafbat .ui .service .mcp ;
22
3+ import com .fasterxml .jackson .core .JsonProcessingException ;
34import com .fasterxml .jackson .databind .ObjectMapper ;
45import com .github .victools .jsonschema .generator .SchemaGenerator ;
56import io .modelcontextprotocol .server .McpAsyncServerExchange ;
1920import java .util .Map ;
2021import java .util .function .BiFunction ;
2122import lombok .RequiredArgsConstructor ;
23+ import lombok .extern .slf4j .Slf4j ;
2224import org .springframework .aop .support .AopUtils ;
2325import org .springframework .core .annotation .AnnotationUtils ;
26+ import org .springframework .http .HttpStatusCode ;
2427import org .springframework .http .ResponseEntity ;
2528import org .springframework .stereotype .Component ;
2629import org .springframework .web .server .ServerWebExchange ;
2730import reactor .core .publisher .Flux ;
2831import reactor .core .publisher .Mono ;
2932
33+ @ Slf4j
3034@ Component
3135@ RequiredArgsConstructor
3236public class McpSpecificationGenerator {
3337 private final SchemaGenerator schemaGenerator ;
34- private final ObjectMapper objectMapper ;
38+ private final ObjectMapper objectMapper = new ObjectMapper () ;
3539
3640 public List <AsyncToolSpecification > convertTool (McpTool controller ) {
3741 List <AsyncToolSpecification > result = new ArrayList <>();
@@ -48,7 +52,7 @@ public List<AsyncToolSpecification> convertTool(McpTool controller) {
4852 return result ;
4953 }
5054
51- public AsyncToolSpecification convertOperation (Method method , Operation annotation , McpTool instance ) {
55+ private AsyncToolSpecification convertOperation (Method method , Operation annotation , McpTool instance ) {
5256 String name = annotation .operationId ();
5357 String description = annotation .description ().isEmpty () ? name : annotation .description ();
5458 return new AsyncToolSpecification (
@@ -61,17 +65,18 @@ public AsyncToolSpecification convertOperation(Method method, Operation annotati
6165 private BiFunction <McpAsyncServerExchange , Map <String , Object >, Mono <CallToolResult >>
6266 methodCall (Method method , Object instance ) {
6367
64- return (ex , v ) -> Mono .deferContextual (ctx -> {
68+ return (ex , args ) -> Mono .deferContextual (ctx -> {
6569 try {
6670 ServerWebExchange serverWebExchange = ctx .get (ServerWebExchange .class );
6771 Mono <Object > result = (Mono <Object >) method .invoke (
6872 instance ,
69- toParams (v , method .getParameters (), ex , serverWebExchange )
73+ toParams (args , method .getParameters (), ex , serverWebExchange )
7074 );
7175 return result .flatMap (this ::toCallResult )
72- .onErrorResume ((e ) -> Mono .just (this .toError (e )));
76+ .onErrorResume ((e ) -> Mono .just (this .toErrorResult (e )));
7377 } catch (IllegalAccessException | InvocationTargetException e ) {
74- return Mono .just (this .toError (e ));
78+ log .warn ("Error invoking method {}: {}" , method .getName (), e .getMessage (), e );
79+ return Mono .just (this .toErrorResult (e ));
7580 }
7681 });
7782 }
@@ -80,31 +85,52 @@ private Mono<CallToolResult> toCallResult(Object result) {
8085 return switch (result ) {
8186 case Mono <?> mono -> mono .map (this ::callToolResult );
8287 case Flux <?> flux -> flux .collectList ().map (this ::callToolResult );
83- case ResponseEntity <?> response -> toCallResult (response . getBody () );
88+ case ResponseEntity <?> response -> reponseToCallResult (response );
8489 case null , default -> Mono .just (this .callToolResult (result ));
8590 };
8691 }
8792
93+ private Mono <CallToolResult > reponseToCallResult (ResponseEntity <?> response ) {
94+ HttpStatusCode statusCode = response .getStatusCode ();
95+ if (statusCode .is2xxSuccessful () || statusCode .is1xxInformational ()) {
96+ return Mono .just (this .callToolResult (response .getBody ()));
97+ } else {
98+ try {
99+ return Mono .just (toErrorResult (objectMapper .writeValueAsString (response .getBody ())));
100+ } catch (JsonProcessingException e ) {
101+ return Mono .just (toErrorResult (e ));
102+ }
103+ }
104+ }
105+
88106 private CallToolResult callToolResult (Object result ) {
89107 try {
90108 return new CallToolResult (
91109 List .of (new McpSchema .TextContent (objectMapper .writeValueAsString (result ))),
92110 false
93111 );
94112 } catch (Exception e ) {
95- return toError (e );
113+ return toErrorResult (e );
96114 }
97115 }
98116
99- protected CallToolResult toError (Throwable e ) {
117+ protected CallToolResult toErrorResult (String body ) {
118+ return new CallToolResult (
119+ List .of (new McpSchema .TextContent (body )),
120+ true
121+ );
122+ }
123+
124+ protected CallToolResult toErrorResult (Throwable e ) {
125+ log .warn ("Error responded to MCP Client: {}" , e .getMessage (), e );
100126 return new CallToolResult (
101127 List .of (new McpSchema .TextContent (e .getMessage ())),
102128 true
103129 );
104130 }
105131
106132 private Object [] toParams (
107- Map <String , Object > v ,
133+ Map <String , Object > mcpArgs ,
108134 Parameter [] parameters ,
109135 McpAsyncServerExchange ex ,
110136 ServerWebExchange serverWebExchange
@@ -117,8 +143,8 @@ private Object[] toParams(
117143 } else if (parameter .getType ().equals (McpAsyncServerExchange .class )) {
118144 values [i ] = ex ;
119145 } else {
120- Object o = v .get (parameter .getName ());
121- if (o != null ) {
146+ Object arg = mcpArgs .get (parameter .getName ());
147+ if (arg != null ) {
122148 Class <?> parameterType = parameter .getType ();
123149 boolean mono = false ;
124150
@@ -129,11 +155,11 @@ private Object[] toParams(
129155 mono = true ;
130156 }
131157
132- if (parameterType .isAssignableFrom (o .getClass ())) {
133- values [i ] = mono ? Mono .just (o ) : o ;
134- } else if (Map .class .isAssignableFrom (o .getClass ())) {
158+ if (parameterType .isAssignableFrom (arg .getClass ())) {
159+ values [i ] = mono ? Mono .just (arg ) : arg ;
160+ } else if (Map .class .isAssignableFrom (arg .getClass ())) {
135161 try {
136- Object obj = objectMapper .convertValue (o , parameterType );
162+ Object obj = objectMapper .convertValue (arg , parameterType );
137163 values [i ] = mono ? Mono .just (obj ) : obj ;
138164 } catch (Exception e ) {
139165 throw new RuntimeException (e );
@@ -149,7 +175,7 @@ private Object[] toParams(
149175 private JsonSchema operationSchema (Method method , McpTool instance ) {
150176 Method annotatedMethod = findAnnotatedMethod (method , instance );
151177
152- Map <String , Object > parameters = new HashMap <>();
178+ Map <String , Object > parametersSchemas = new HashMap <>();
153179 List <String > required = new ArrayList <>();
154180 Parameter [] annotatedParameters = annotatedMethod .getParameters ();
155181 Parameter [] methodParameters = method .getParameters ();
@@ -163,14 +189,14 @@ private JsonSchema operationSchema(Method method, McpTool instance) {
163189 if (parameterAnnotation .required ()) {
164190 required .add (methodParameter .getName ());
165191 }
166- parameters .put (
192+ parametersSchemas .put (
167193 methodParameter .getName (),
168194 getTypeSchema (methodParameter )
169195 );
170196 }
171197 }
172198 return new JsonSchema (
173- "object" , parameters , required ,
199+ "object" , parametersSchemas , required ,
174200 false ,
175201 null , null
176202 );
0 commit comments