Bug description

While input schema generation includes Jackson annotations like @JsonPropertyDescription, output schema generation does not. The root cause of this is JsonSchemaGenerator.internalGenerateFromClass creating its schema generator config from scratch instead of using TYPE_SCHEMA_GENERATOR or at least including the Jackson module manually.

Environment Spring AI 1.1.0-M2, reactive mode, Java 24.

Steps to reproduce Use a @McpTool method with a return type that uses Jackson description annotations.

Expected behavior The Jackson annotations are included in the schema generation.

Minimal Complete Reproducible example

@SpringBootApplication
class Application {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            runApplication<Application>(*args)
        }
    }

    data class Input(@JsonPropertyDescription("Test Input") val value: String)
    data class Output(@JsonPropertyDescription("Test Output") val value: String)

    @McpTool
    fun test(@McpToolParam(description = "input") input: Input): Output {
        return Output(input.value)
    }
}

Tool listing response:

{
  "name": "test",
  "title": "test",
  "description": "",
  "inputSchema": {
    "type": "object",
    "properties": {
      "input": {
        "type": "object",
        "properties": {
          "value": {
            "type": "string",
            "description": "Test Input"
          }
        },
        "required": [
          "value"
        ],
        "description": "input"
      }
    },
    "required": [
      "input"
    ]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "value": {
        "type": "string"
      }
    },
    "$schema": "https://json-schema.org/draft/2020-12/schema"
  },
  "annotations": {
    "title": "",
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": false,
    "openWorldHint": true
  }
}

Comment From: rnett

This is even worse than I thought, since optionality and nullability come from Jackson. A nullable type in your output schema will cause validation errors on the response, since the nullability isn't recorded in the schema.

Comment From: okohub

Looks like inputSchema and outputSchema generation differs in method calls. May be there is a reason behind this decision.

Input Schema: "generateForMethodInput" which is honoring descriptions

String inputSchema = JsonSchemaGenerator.generateForMethodInput(mcpToolMethod);
````

Output Schema: "generateFromType" which is more like honoring base structure. 

```java
toolBuilder.outputSchema(this.getJsonMapper(),
                                JsonSchemaGenerator.generateFromType(mcpToolMethod.getGenericReturnType()));

We may agree on architectural decision (like outputSchema is only for validation), but nullability seems like a bug.

Comment From: HDHXTM

The framework uses org.springaicommunity.mcp.method.tool.utils.JsonSchemaGenerator#internalGenerateFromType to generate the output schema. code:

        SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12,
                OptionPreset.PLAIN_JSON);
        SchemaGeneratorConfig config = configBuilder.with(Option.EXTRA_OPEN_API_FORMAT_VALUES)
            .without(Option.FLATTENED_ENUMS_FROM_TOSTRING)
            .build();

        SchemaGenerator generator = new SchemaGenerator(config);
        JsonNode jsonSchema = generator.generateSchema(type);
        return jsonSchema.toPrettyString();

It is my test code :


import cn.hutool.core.util.ReflectUtil;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.victools.jsonschema.generator.*;
import lombok.Data;
import org.springaicommunity.mcp.method.tool.utils.JsonSchemaGenerator;

@Data
public class CourseInfoDto {
    @JsonPropertyDescription("课程id")
    private String id;

    /**
     * 课程名称
     */
    @JsonPropertyDescription("课程名称")
    private String courseName;


    public static void main(String[] args) {
        SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12,
                OptionPreset.PLAIN_JSON);
        SchemaGeneratorConfigBuilder builder = configBuilder.with(Option.EXTRA_OPEN_API_FORMAT_VALUES)
                .without(Option.FLATTENED_ENUMS_FROM_TOSTRING);
        builder.forFields().withDescriptionResolver(a -> {
            JsonPropertyDescription propertyAnnotation = a.getAnnotationConsideringFieldAndGetterIfSupported(JsonPropertyDescription.class);
            if (propertyAnnotation != null) {
                return propertyAnnotation.value();
            }
            return null;
        });
        SchemaGeneratorConfig config = builder.build();

        SchemaGenerator generator = new SchemaGenerator(config);
        JsonNode jsonSchema = generator.generateSchema(CourseInfoDto.class);
        System.out.println(jsonSchema.toPrettyString());


        JsonSchemaGenerator g = new JsonSchemaGenerator();
        SchemaGenerator schemaGenerator = (SchemaGenerator) ReflectUtil.getFieldValue(g, "TYPE_SCHEMA_GENERATOR");
        System.out.println(schemaGenerator.generateSchema(CourseInfoDto.class).toPrettyString());
    }
}

Running my code produces the following output:

{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "courseName" : {
      "type" : "string",
      "description" : "课程名称"
    },
    "id" : {
      "type" : "string",
      "description" : "课程id"
    }
  }
}
{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "courseName" : {
      "type" : "string",
      "description" : "课程名称"
    },
    "id" : {
      "type" : "string",
      "description" : "课程id"
    }
  },
  "required" : [ "courseName", "id" ]
}

So why doesn't the author use org.springaicommunity.mcp.method.tool.utils.JsonSchemaGenerator#TYPE_SCHEMA_GENERATOR to generate the output schema?

Comment From: SuperZ1999

This is even worse than I thought, since optionality and nullability come from Jackson. A nullable type in your output schema will cause validation errors on the response, since the nullability isn't recorded in the schema.

@rnett Hello, have you resolved this? I'm facing the same problem.

Comment From: rnett

I have not, I switched to the Kotlin SDK (which lets and makes you generate the output schema yourself).