@@ -587,6 +587,117 @@ private String getServiceClassName(AxisService service) {
587587 return null ;
588588 }
589589
590+ /**
591+ * Auto-generate a JSON Schema from the Java service method's parameter type.
592+ *
593+ * <p>Looks up the service class, finds the method matching the operation name,
594+ * and introspects the parameter POJO's fields to produce a schema. This is the
595+ * "Option 2" fallback when no explicit {@code mcpInputSchema} is set in
596+ * services.xml.
597+ *
598+ * <p>Supports: primitives (int/long/double/boolean/String), arrays, and
599+ * nested POJOs (one level). Returns null if introspection fails for any reason.
600+ *
601+ * @param service the Axis2 service descriptor
602+ * @param operationName the operation (method) name
603+ * @return an ObjectNode containing the JSON Schema, or null
604+ */
605+ private com .fasterxml .jackson .databind .node .ObjectNode generateSchemaFromServiceClass (
606+ AxisService service , String operationName ) {
607+ try {
608+ String className = getServiceClassName (service );
609+ if (className == null ) return null ;
610+
611+ Class <?> serviceClass = Thread .currentThread ().getContextClassLoader ().loadClass (className );
612+ java .lang .reflect .Method targetMethod = null ;
613+ for (java .lang .reflect .Method m : serviceClass .getMethods ()) {
614+ if (m .getName ().equals (operationName ) && m .getParameterCount () == 1 ) {
615+ targetMethod = m ;
616+ break ;
617+ }
618+ }
619+ if (targetMethod == null ) return null ;
620+
621+ Class <?> paramType = targetMethod .getParameterTypes ()[0 ];
622+ // Skip primitives and common JDK types — only introspect POJOs
623+ if (paramType .isPrimitive () || paramType == String .class
624+ || paramType .getName ().startsWith ("java." )) {
625+ return null ;
626+ }
627+
628+ com .fasterxml .jackson .databind .ObjectMapper mapper = io .swagger .v3 .core .util .Json .mapper ();
629+ com .fasterxml .jackson .databind .node .ObjectNode schema = mapper .createObjectNode ();
630+ schema .put ("type" , "object" );
631+ com .fasterxml .jackson .databind .node .ObjectNode properties = schema .putObject ("properties" );
632+ com .fasterxml .jackson .databind .node .ArrayNode required = schema .putArray ("required" );
633+
634+ for (java .lang .reflect .Method getter : paramType .getMethods ()) {
635+ String name = getter .getName ();
636+ if (!name .startsWith ("get" ) || name .equals ("getClass" ) || getter .getParameterCount () != 0 ) {
637+ if (name .startsWith ("is" ) && getter .getParameterCount () == 0
638+ && (getter .getReturnType () == boolean .class || getter .getReturnType () == Boolean .class )) {
639+ // boolean getter: isNormalizeWeights -> normalizeWeights
640+ String fieldName = Character .toLowerCase (name .charAt (2 )) + name .substring (3 );
641+ com .fasterxml .jackson .databind .node .ObjectNode prop = properties .putObject (fieldName );
642+ prop .put ("type" , "boolean" );
643+ }
644+ continue ;
645+ }
646+ // getWeights -> weights
647+ String fieldName = Character .toLowerCase (name .charAt (3 )) + name .substring (4 );
648+ Class <?> returnType = getter .getReturnType ();
649+
650+ com .fasterxml .jackson .databind .node .ObjectNode prop = properties .putObject (fieldName );
651+ mapJavaTypeToJsonSchema (returnType , getter .getGenericReturnType (), prop );
652+ }
653+
654+ return schema ;
655+ } catch (Exception e ) {
656+ log .debug ("[MCP] Could not auto-generate schema for " + service .getName ()
657+ + "/" + operationName + ": " + e .getMessage ());
658+ return null ;
659+ }
660+ }
661+
662+ /**
663+ * Maps a Java type to a JSON Schema type/format in the given ObjectNode.
664+ */
665+ private void mapJavaTypeToJsonSchema (Class <?> type , java .lang .reflect .Type genericType ,
666+ com .fasterxml .jackson .databind .node .ObjectNode prop ) {
667+ if (type == int .class || type == Integer .class ) {
668+ prop .put ("type" , "integer" );
669+ } else if (type == long .class || type == Long .class ) {
670+ prop .put ("type" , "integer" );
671+ } else if (type == double .class || type == Double .class || type == float .class || type == Float .class ) {
672+ prop .put ("type" , "number" );
673+ } else if (type == boolean .class || type == Boolean .class ) {
674+ prop .put ("type" , "boolean" );
675+ } else if (type == String .class ) {
676+ prop .put ("type" , "string" );
677+ } else if (type .isArray ()) {
678+ prop .put ("type" , "array" );
679+ com .fasterxml .jackson .databind .node .ObjectNode items = prop .putObject ("items" );
680+ Class <?> componentType = type .getComponentType ();
681+ if (componentType .isArray ()) {
682+ // double[][] -> array of arrays of numbers
683+ items .put ("type" , "array" );
684+ com .fasterxml .jackson .databind .node .ObjectNode innerItems = items .putObject ("items" );
685+ mapJavaTypeToJsonSchema (componentType .getComponentType (), null , innerItems );
686+ } else {
687+ mapJavaTypeToJsonSchema (componentType , null , items );
688+ }
689+ } else if (java .util .List .class .isAssignableFrom (type ) && genericType instanceof java .lang .reflect .ParameterizedType ) {
690+ prop .put ("type" , "array" );
691+ java .lang .reflect .Type [] typeArgs = ((java .lang .reflect .ParameterizedType ) genericType ).getActualTypeArguments ();
692+ if (typeArgs .length > 0 && typeArgs [0 ] instanceof Class ) {
693+ com .fasterxml .jackson .databind .node .ObjectNode items = prop .putObject ("items" );
694+ mapJavaTypeToJsonSchema ((Class <?>) typeArgs [0 ], null , items );
695+ }
696+ } else {
697+ prop .put ("type" , "object" );
698+ }
699+ }
700+
590701 /**
591702 * Check if a package is included in the configured resource packages.
592703 */
@@ -820,11 +931,25 @@ public String generateMcpCatalogJson(HttpServletRequest request) {
820931 schema .putArray ("required" );
821932 }
822933 } else {
934+ // Option 2: auto-generate schema from Java method parameter type.
935+ // Introspects the service class to find the method matching
936+ // this operation name, then reflects on the request POJO's
937+ // fields to build a JSON Schema. Falls back to empty schema
938+ // if introspection fails (e.g., no ServiceClass parameter,
939+ // method not found, or primitive parameters).
823940 com .fasterxml .jackson .databind .node .ObjectNode schema =
824- toolNode .putObject ("inputSchema" );
825- schema .put ("type" , "object" );
826- schema .putObject ("properties" );
827- schema .putArray ("required" );
941+ generateSchemaFromServiceClass (service , opName );
942+ if (schema != null ) {
943+ toolNode .set ("inputSchema" , schema );
944+ log .debug ("[MCP] Auto-generated inputSchema for "
945+ + service .getName () + "/" + opName
946+ + " from Java type introspection" );
947+ } else {
948+ schema = toolNode .putObject ("inputSchema" );
949+ schema .put ("type" , "object" );
950+ schema .putObject ("properties" );
951+ schema .putArray ("required" );
952+ }
828953 }
829954
830955 toolNode .put ("endpoint" , "POST " + path );
0 commit comments