This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 3138029cce60e5c55f6530e237116898d6e437c1
Author: Robert Lazarski <[email protected]>
AuthorDate: Sat Apr 11 11:43:03 2026 -1000

    Auto-generate MCP inputSchema from Java method parameter types
    
    When mcpInputSchema is not set in services.xml, the MCP catalog
    generator now introspects the service class to find the method
    matching the operation name and generates a JSON Schema from the
    request POJO's getter methods.
    
    Supported types: int/long -> integer, double/float -> number,
    boolean -> boolean, String -> string, arrays -> array (including
    nested arrays like double[][]), List<T> -> array with typed items,
    POJOs -> object.
    
    Precedence: explicit mcpInputSchema in services.xml always wins.
    Auto-generation is the fallback when no schema is declared.
    
    Limitation: requires ServiceClass parameter in services.xml.
    Spring-bean-only services (SpringBeanName without ServiceClass)
    cannot be introspected at catalog generation time because the
    bean class is resolved by Spring at invocation time, not at
    deployment time. These services still need mcpInputSchema.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../apache/axis2/openapi/OpenApiSpecGenerator.java | 133 ++++++++++++++++++++-
 1 file changed, 129 insertions(+), 4 deletions(-)

diff --git 
a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java
 
b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java
index 33996e5c1a..5c6fbc25ab 100644
--- 
a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java
+++ 
b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java
@@ -587,6 +587,117 @@ public class OpenApiSpecGenerator {
         return null;
     }
 
+    /**
+     * Auto-generate a JSON Schema from the Java service method's parameter 
type.
+     *
+     * <p>Looks up the service class, finds the method matching the operation 
name,
+     * and introspects the parameter POJO's fields to produce a schema. This 
is the
+     * "Option 2" fallback when no explicit {@code mcpInputSchema} is set in
+     * services.xml.
+     *
+     * <p>Supports: primitives (int/long/double/boolean/String), arrays, and
+     * nested POJOs (one level). Returns null if introspection fails for any 
reason.
+     *
+     * @param service the Axis2 service descriptor
+     * @param operationName the operation (method) name
+     * @return an ObjectNode containing the JSON Schema, or null
+     */
+    private com.fasterxml.jackson.databind.node.ObjectNode 
generateSchemaFromServiceClass(
+            AxisService service, String operationName) {
+        try {
+            String className = getServiceClassName(service);
+            if (className == null) return null;
+
+            Class<?> serviceClass = 
Thread.currentThread().getContextClassLoader().loadClass(className);
+            java.lang.reflect.Method targetMethod = null;
+            for (java.lang.reflect.Method m : serviceClass.getMethods()) {
+                if (m.getName().equals(operationName) && m.getParameterCount() 
== 1) {
+                    targetMethod = m;
+                    break;
+                }
+            }
+            if (targetMethod == null) return null;
+
+            Class<?> paramType = targetMethod.getParameterTypes()[0];
+            // Skip primitives and common JDK types — only introspect POJOs
+            if (paramType.isPrimitive() || paramType == String.class
+                    || paramType.getName().startsWith("java.")) {
+                return null;
+            }
+
+            com.fasterxml.jackson.databind.ObjectMapper mapper = 
io.swagger.v3.core.util.Json.mapper();
+            com.fasterxml.jackson.databind.node.ObjectNode schema = 
mapper.createObjectNode();
+            schema.put("type", "object");
+            com.fasterxml.jackson.databind.node.ObjectNode properties = 
schema.putObject("properties");
+            com.fasterxml.jackson.databind.node.ArrayNode required = 
schema.putArray("required");
+
+            for (java.lang.reflect.Method getter : paramType.getMethods()) {
+                String name = getter.getName();
+                if (!name.startsWith("get") || name.equals("getClass") || 
getter.getParameterCount() != 0) {
+                    if (name.startsWith("is") && getter.getParameterCount() == 0
+                            && (getter.getReturnType() == boolean.class || 
getter.getReturnType() == Boolean.class)) {
+                        // boolean getter: isNormalizeWeights -> 
normalizeWeights
+                        String fieldName = 
Character.toLowerCase(name.charAt(2)) + name.substring(3);
+                        com.fasterxml.jackson.databind.node.ObjectNode prop = 
properties.putObject(fieldName);
+                        prop.put("type", "boolean");
+                    }
+                    continue;
+                }
+                // getWeights -> weights
+                String fieldName = Character.toLowerCase(name.charAt(3)) + 
name.substring(4);
+                Class<?> returnType = getter.getReturnType();
+
+                com.fasterxml.jackson.databind.node.ObjectNode prop = 
properties.putObject(fieldName);
+                mapJavaTypeToJsonSchema(returnType, 
getter.getGenericReturnType(), prop);
+            }
+
+            return schema;
+        } catch (Exception e) {
+            log.debug("[MCP] Could not auto-generate schema for " + 
service.getName()
+                    + "/" + operationName + ": " + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * Maps a Java type to a JSON Schema type/format in the given ObjectNode.
+     */
+    private void mapJavaTypeToJsonSchema(Class<?> type, java.lang.reflect.Type 
genericType,
+            com.fasterxml.jackson.databind.node.ObjectNode prop) {
+        if (type == int.class || type == Integer.class) {
+            prop.put("type", "integer");
+        } else if (type == long.class || type == Long.class) {
+            prop.put("type", "integer");
+        } else if (type == double.class || type == Double.class || type == 
float.class || type == Float.class) {
+            prop.put("type", "number");
+        } else if (type == boolean.class || type == Boolean.class) {
+            prop.put("type", "boolean");
+        } else if (type == String.class) {
+            prop.put("type", "string");
+        } else if (type.isArray()) {
+            prop.put("type", "array");
+            com.fasterxml.jackson.databind.node.ObjectNode items = 
prop.putObject("items");
+            Class<?> componentType = type.getComponentType();
+            if (componentType.isArray()) {
+                // double[][] -> array of arrays of numbers
+                items.put("type", "array");
+                com.fasterxml.jackson.databind.node.ObjectNode innerItems = 
items.putObject("items");
+                mapJavaTypeToJsonSchema(componentType.getComponentType(), 
null, innerItems);
+            } else {
+                mapJavaTypeToJsonSchema(componentType, null, items);
+            }
+        } else if (java.util.List.class.isAssignableFrom(type) && genericType 
instanceof java.lang.reflect.ParameterizedType) {
+            prop.put("type", "array");
+            java.lang.reflect.Type[] typeArgs = 
((java.lang.reflect.ParameterizedType) genericType).getActualTypeArguments();
+            if (typeArgs.length > 0 && typeArgs[0] instanceof Class) {
+                com.fasterxml.jackson.databind.node.ObjectNode items = 
prop.putObject("items");
+                mapJavaTypeToJsonSchema((Class<?>) typeArgs[0], null, items);
+            }
+        } else {
+            prop.put("type", "object");
+        }
+    }
+
     /**
      * Check if a package is included in the configured resource packages.
      */
@@ -820,11 +931,25 @@ public class OpenApiSpecGenerator {
                             schema.putArray("required");
                         }
                     } else {
+                        // Option 2: auto-generate schema from Java method 
parameter type.
+                        // Introspects the service class to find the method 
matching
+                        // this operation name, then reflects on the request 
POJO's
+                        // fields to build a JSON Schema. Falls back to empty 
schema
+                        // if introspection fails (e.g., no ServiceClass 
parameter,
+                        // method not found, or primitive parameters).
                         com.fasterxml.jackson.databind.node.ObjectNode schema =
-                                toolNode.putObject("inputSchema");
-                        schema.put("type", "object");
-                        schema.putObject("properties");
-                        schema.putArray("required");
+                                generateSchemaFromServiceClass(service, 
opName);
+                        if (schema != null) {
+                            toolNode.set("inputSchema", schema);
+                            log.debug("[MCP] Auto-generated inputSchema for "
+                                    + service.getName() + "/" + opName
+                                    + " from Java type introspection");
+                        } else {
+                            schema = toolNode.putObject("inputSchema");
+                            schema.put("type", "object");
+                            schema.putObject("properties");
+                            schema.putArray("required");
+                        }
                     }
 
                     toolNode.put("endpoint", "POST " + path);

Reply via email to