Skip to content

Allowed to set a custom JsonSchemaGeneratorOptions for MCP tool schema #543

Closed

Description

@fvdcx

Say I have such a tool input object class

    public class TemporalData
    {
        [JsonPropertyName("dateTimeValue")]
        public DateTime DateTimeValue { get; set; }

        [JsonPropertyName("dateTimeOffsetValue")]
        public DateTimeOffset DateTimeOffsetValue { get; set; }

        [JsonPropertyName("timeSpanValue")]
        public TimeSpan TimeSpanValue { get; set; }
    }

Its corresponding input schema (auto-generated by sdk 0.3.0, no hack here)is

{
        "name": "test",
        "description": "Test tool that accepts a TemporalData object with DateTime, DateTimeOffset, and TimeSpan properties.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "input": {
              "description": "A TemporalData object with temporal properties",
              "type": "object",
              "properties": {
                "dateTimeValue": {
                  "type": "string"
                },
                "dateTimeOffsetValue": {
                  "type": "string"
                },
                "timeSpanValue": {
                  "$comment": "Represents a System.TimeSpan value.",
                  "type": "string"
                }
              }
            }
          },
          "required": [
            "input"
          ]
        }
      }

Three date time related types are there

  • DateTime - dateTimeValue
  • DateTimeOffSet - dateTimeOffsetValue
  • TimeSpan - timeSpanValue
    As you can see, only TimeSpan type has been added a comment "$comment": "Represents a System.TimeSpan value.", automatically.

However, it's super important for AI agent to understand the field type especically when the type isn't supported in JSON natively like datetime, so sdk should provide a way to allow me to set a JsonSchemaGeneratorOptions to add a TypeMapping,

options.TypeMappings.Add(
  new JsonSchemaTypeMapping<DateTime>((_, schema) =>
  {
    schema.Type    = JsonSchemaType.String;
    schema.Format  = "date-time";
    schema.Comment = "Represents a System.DateTime";
    return true;
  })
);

Then for DateTime type in inputSchema, it'll be like

"dateTimeValue": {
  "$comment": "Represents a System.DateTime",
  "type": "string",
  "format": "date-time"
}

Let me know if this makes sense

Activity

  1. stephentoub commented on Jun 25, 2025

    @stephentoub
    Contributor

    For DateTime, DateTimeOffset, and TimeSpan specifically, that should automatically be handled by dotnet/extensions#6540.

    Even without that change, however, you can already control the schema being generated via the TransformSchemaNode on the SchemaCreateOptions passed to McpServerTool.Create. For example, this code:

    using ModelContextProtocol.Server;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    var tool = McpServerTool.Create((TemporalData input) => "received data");
    
    Console.WriteLine(JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, new JsonSerializerOptions { WriteIndented = true }));
    
    public class TemporalData
    {
        [JsonPropertyName("dateTimeValue")]
        public DateTime DateTimeValue { get; set; }
    
        [JsonPropertyName("dateTimeOffsetValue")]
        public DateTimeOffset DateTimeOffsetValue { get; set; }
    
        [JsonPropertyName("timeSpanValue")]
        public TimeSpan TimeSpanValue { get; set; }
    }

    outputs schema like you cite in your issue:

    {
      "type": "object",
      "properties": {
        "input": {
          "type": "object",
          "properties": {
            "dateTimeValue": {
              "type": "string"
            },
            "dateTimeOffsetValue": {
              "type": "string"
            },
            "timeSpanValue": {
              "$comment": "Represents a System.TimeSpan value.",
              "type": "string"
            }
          }
        }
      },
      "required": [
        "input"
      ]
    }

    But, if I change the Create call to be this:

    var tool = McpServerTool.Create((TemporalData input) => "received data", new()
    {
        SchemaCreateOptions = new()
        {
            TransformSchemaNode = (ctx, node) =>
            {
                Type? t = ctx.PropertyInfo?.PropertyType;
                if (t == typeof(DateTime) || t == typeof(DateTimeOffset))
                {
                    node["format"] = "date-time";
                }
                else if (t == typeof(TimeSpan))
                {
                    node["format"] = "duration";
                }
    
                return node;
            },
        },
    });

    then I get output like this:

    {
      "type": "object",
      "properties": {
        "input": {
          "type": "object",
          "properties": {
            "dateTimeValue": {
              "type": "string",
              "format": "date-time"
            },
            "dateTimeOffsetValue": {
              "type": "string",
              "format": "date-time"
            },
            "timeSpanValue": {
              "$comment": "Represents a System.TimeSpan value.",
              "type": "string",
              "format": "duration"
            }
          }
        }
      },
      "required": [
        "input"
      ]
    }
  2. fvdcx commented on Jun 26, 2025

    @fvdcx
    Author

    Yeah, that should work. I see your example is using the dynamic way to create a tool

    var tool = McpServerTool.Create((TemporalData input) => "received data", new()
    {
        SchemaCreateOptions = new()
        {
            TransformSchemaNode = (ctx, node) =>
            {
                Type? t = ctx.PropertyInfo?.PropertyType;
                if (t == typeof(DateTime) || t == typeof(DateTimeOffset))
                {
                    node["format"] = "date-time";
                }
                else if (t == typeof(TimeSpan))
                {
                    node["format"] = "duration";
                }
    
                return node;
            },
        },
    });
    

    Is it possible to add SchemaCreateOptions on a specific static mcp tool (existing method with adding mcpservertool attribute)

            [McpServerTool(Name = "test_tool"), Description("")]
            public async Task<CallToolResponse> TestToolMethod(
                [Description("")] Foo foo)
            {
            }
    
  3. eiriktsarpalis commented on Jun 26, 2025

    @eiriktsarpalis
    Member

    I think we can close this given that dotnet/extensions#6540 got merged. Note that this won't be addressed in MCP until a new version of MEAI.Abstractions has been published.

  4. stephentoub commented on Jun 26, 2025

    @stephentoub
    Contributor

    Is it possible to add SchemaCreateOptions on a specific static mcp tool (existing method with adding mcpservertool attribute)

    Not today, no. In theory we could extend the attribute with the ability to supply a Type name and a member name and use reflection to access the options instance, but that starts to get complicated, and the attribute-based scheme is intended to represent the simple/majority cases. The McpServerTool type is provided when you need much more control, with the Create methods, with a DelegatingMcpServerTool type to make it easy to wrap another McpServerTool instance and override whatever members you need to, to write your own derived type, etc. The attribute-based support is itself just a small wrapper around using that class; you can see here that it's just getting all methods with the relevant attribute and calling McpServerTool.Create with each; you can do the same with your own helper if you want more customization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions