From 958d1fd6f1b81b12799ab91622165dc2faf838aa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 13:18:06 +0000
Subject: [PATCH 01/10] Initial plan
From 2f84c3ea8cc1009f8f8a126c325a641f16fd1913 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 13:50:06 +0000
Subject: [PATCH 02/10] Add JsonSerializerOptions property to McpServerOptions
and update builder extensions
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../Server/McpServerOptions.cs | 21 +++++
.../McpServerBuilderExtensions.cs | 47 ++++++----
.../McpServerJsonSerializerOptionsTests.cs | 94 +++++++++++++++++++
3 files changed, 142 insertions(+), 20 deletions(-)
create mode 100644 tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
diff --git a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs
index 618e87d58..f3e9dcc68 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs
@@ -1,4 +1,5 @@
using ModelContextProtocol.Protocol;
+using System.Text.Json;
namespace ModelContextProtocol.Server;
@@ -60,6 +61,26 @@ public sealed class McpServerOptions
///
public string? ServerInstructions { get; set; }
+ ///
+ /// Gets or sets the default JSON serializer options to use for tools, prompts, and resources.
+ ///
+ ///
+ ///
+ /// This property provides server-wide default serialization settings that will be used
+ /// by all tools, prompts, and resources unless they explicitly specify their own
+ /// during registration.
+ ///
+ ///
+ /// If not set, defaults to .
+ ///
+ ///
+ /// This is useful for configuring settings like JsonNumberHandling.AllowNamedFloatingPointLiterals
+ /// to handle special floating-point values like , ,
+ /// and .
+ ///
+ ///
+ public JsonSerializerOptions? JsonSerializerOptions { get; set; }
+
///
/// Gets or sets whether to create a new service provider scope for each handled request.
///
diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
index d4c338262..2f4ff2a5c 100644
--- a/src/ModelContextProtocol/McpServerBuilderExtensions.cs
+++ b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
@@ -45,8 +45,8 @@ public static partial class McpServerBuilderExtensions
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
- services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions })));
+ services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
+ services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
}
}
@@ -93,7 +93,7 @@ public static partial class McpServerBuilderExtensions
builder.Services.AddSingleton(services => McpServerTool.Create(
toolMethod,
toolMethod.IsStatic ? null : target,
- new() { Services = services, SerializerOptions = serializerOptions }));
+ new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }));
}
}
@@ -149,8 +149,8 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions }) :
- services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions })));
+ services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
+ services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
}
}
}
@@ -232,8 +232,8 @@ where t.GetCustomAttribute() is not null
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
- services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
- services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions })));
+ services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
+ services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
}
}
@@ -277,7 +277,7 @@ where t.GetCustomAttribute() is not null
{
if (promptMethod.GetCustomAttribute() is not null)
{
- builder.Services.AddSingleton(services => McpServerPrompt.Create(promptMethod, target, new() { Services = services, SerializerOptions = serializerOptions }));
+ builder.Services.AddSingleton(services => McpServerPrompt.Create(promptMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }));
}
}
@@ -333,8 +333,8 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
- services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
- services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions })));
+ services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
+ services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
}
}
}
@@ -394,6 +394,7 @@ where t.GetCustomAttribute() is not null
/// Adds instances to the service collection backing .
/// The resource type.
/// The builder instance.
+ /// The serializer options governing resource parameter marshalling.
/// The builder provided in .
/// is .
///
@@ -405,7 +406,8 @@ where t.GetCustomAttribute() is not null
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TResourceType>(
- this IMcpServerBuilder builder)
+ this IMcpServerBuilder builder,
+ JsonSerializerOptions? serializerOptions = null)
{
Throw.IfNull(builder);
@@ -414,8 +416,8 @@ where t.GetCustomAttribute() is not null
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ?
- services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) :
- services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services })));
+ services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
+ services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
}
}
@@ -426,6 +428,7 @@ where t.GetCustomAttribute() is not null
/// The resource type.
/// The builder instance.
/// The target instance from which the prompts should be sourced.
+ /// The serializer options governing resource parameter marshalling.
/// The builder provided in .
/// is .
///
@@ -443,7 +446,8 @@ where t.GetCustomAttribute() is not null
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods)] TResourceType>(
this IMcpServerBuilder builder,
- TResourceType target)
+ TResourceType target,
+ JsonSerializerOptions? serializerOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(target);
@@ -457,7 +461,7 @@ where t.GetCustomAttribute() is not null
{
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
- builder.Services.AddSingleton(services => McpServerResource.Create(resourceTemplateMethod, target, new() { Services = services }));
+ builder.Services.AddSingleton(services => McpServerResource.Create(resourceTemplateMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }));
}
}
@@ -489,6 +493,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
/// Adds instances to the service collection backing .
/// The builder instance.
/// Types with marked methods to add as resources to the server.
+ /// The serializer options governing resource parameter marshalling.
/// The builder provided in .
/// is .
/// is .
@@ -498,7 +503,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
/// instance for each. For instance methods, an instance will be constructed for each invocation of the resource.
///
[RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable resourceTemplateTypes)
+ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable resourceTemplateTypes, JsonSerializerOptions? serializerOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(resourceTemplateTypes);
@@ -512,8 +517,8 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ?
- services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) :
- services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services })));
+ services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
+ services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
}
}
}
@@ -526,6 +531,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
/// Adds types marked with the attribute from the given assembly as resources to the server.
///
/// The builder instance.
+ /// The serializer options governing resource parameter marshalling.
/// The assembly to load the types from. If , the calling assembly will be used.
/// The builder provided in .
/// is .
@@ -550,7 +556,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
///
///
[RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder builder, Assembly? resourceAssembly = null)
+ public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder builder, Assembly? resourceAssembly = null, JsonSerializerOptions? serializerOptions = null)
{
Throw.IfNull(builder);
@@ -559,7 +565,8 @@ public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder
return builder.WithResources(
from t in resourceAssembly.GetTypes()
where t.GetCustomAttribute() is not null
- select t);
+ select t,
+ serializerOptions);
}
#endregion
diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
new file mode 100644
index 000000000..038676627
--- /dev/null
+++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
@@ -0,0 +1,94 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Server;
+using ModelContextProtocol.Tests.Utils;
+using System.ComponentModel;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace ModelContextProtocol.Tests.Configuration;
+
+public class McpServerJsonSerializerOptionsTests : ClientServerTestBase
+{
+ public McpServerJsonSerializerOptionsTests(ITestOutputHelper testOutputHelper)
+ : base(testOutputHelper)
+ {
+ }
+
+ private class SpecialNumbers
+ {
+ public double PositiveInfinity { get; set; }
+ public double NegativeInfinity { get; set; }
+ public double NotANumber { get; set; }
+ }
+
+ protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
+ {
+ // Configure server-wide JsonSerializerOptions to allow named floating point literals
+ var customOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
+ {
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
+ };
+
+ services.Configure(options =>
+ {
+ options.JsonSerializerOptions = customOptions;
+ });
+
+ // Register a tool that will use the server-wide JsonSerializerOptions
+ // The null serializerOptions parameter should cause it to use McpServerOptions.JsonSerializerOptions
+ services.AddSingleton(sp =>
+ {
+ var serverOptions = sp.GetRequiredService>().Value;
+ return McpServerTool.Create(
+ () => new SpecialNumbers
+ {
+ PositiveInfinity = double.PositiveInfinity,
+ NegativeInfinity = double.NegativeInfinity,
+ NotANumber = double.NaN
+ },
+ new McpServerToolCreateOptions
+ {
+ Name = "GetSpecialNumbers",
+ Description = "Returns special floating point values",
+ UseStructuredContent = true,
+ SerializerOptions = serverOptions.JsonSerializerOptions,
+ Services = sp
+ });
+ });
+ }
+
+ [Fact]
+ public async Task ServerWide_JsonSerializerOptions_Applied_To_Tools()
+ {
+ // Arrange
+ McpClient client = await CreateMcpClientForServer();
+
+ // Act
+ IList tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
+ CallToolResult result = await client.CallToolAsync("GetSpecialNumbers", cancellationToken: TestContext.Current.CancellationToken);
+
+ // Assert
+ Assert.NotNull(tools);
+ Assert.Single(tools);
+ Assert.Equal("GetSpecialNumbers", tools[0].Name);
+
+ // Verify the result contains structured content with special numbers
+ Assert.NotNull(result);
+ Assert.NotNull(result.StructuredContent);
+
+ var structuredContent = JsonSerializer.Deserialize(
+ result.StructuredContent.ToString(),
+ new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
+ {
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
+ });
+
+ Assert.NotNull(structuredContent);
+ Assert.True(double.IsPositiveInfinity(structuredContent.PositiveInfinity));
+ Assert.True(double.IsNegativeInfinity(structuredContent.NegativeInfinity));
+ Assert.True(double.IsNaN(structuredContent.NotANumber));
+ }
+}
From bb8d2a5302f257dd1db573873cd48ff307c2d037 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 14:08:26 +0000
Subject: [PATCH 03/10] Fix service resolution to use GetService instead of
GetRequiredService
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../McpServerBuilderExtensions.cs | 30 +++---
.../McpServerJsonSerializerOptionsTests.cs | 101 +++++++-----------
2 files changed, 55 insertions(+), 76 deletions(-)
diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
index 2f4ff2a5c..57ba5d854 100644
--- a/src/ModelContextProtocol/McpServerBuilderExtensions.cs
+++ b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
@@ -45,8 +45,8 @@ public static partial class McpServerBuilderExtensions
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
- services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
+ services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
+ services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
}
}
@@ -93,7 +93,7 @@ public static partial class McpServerBuilderExtensions
builder.Services.AddSingleton(services => McpServerTool.Create(
toolMethod,
toolMethod.IsStatic ? null : target,
- new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }));
+ new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }));
}
}
@@ -149,8 +149,8 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
- services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
+ services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
+ services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
}
}
}
@@ -232,8 +232,8 @@ where t.GetCustomAttribute() is not null
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
- services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
- services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
+ services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
+ services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
}
}
@@ -277,7 +277,7 @@ where t.GetCustomAttribute() is not null
{
if (promptMethod.GetCustomAttribute() is not null)
{
- builder.Services.AddSingleton(services => McpServerPrompt.Create(promptMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }));
+ builder.Services.AddSingleton(services => McpServerPrompt.Create(promptMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }));
}
}
@@ -333,8 +333,8 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
- services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
- services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
+ services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
+ services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
}
}
}
@@ -416,8 +416,8 @@ where t.GetCustomAttribute() is not null
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ?
- services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
- services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
+ services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
+ services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
}
}
@@ -461,7 +461,7 @@ where t.GetCustomAttribute() is not null
{
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
- builder.Services.AddSingleton(services => McpServerResource.Create(resourceTemplateMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }));
+ builder.Services.AddSingleton(services => McpServerResource.Create(resourceTemplateMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }));
}
}
@@ -517,8 +517,8 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ?
- services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions }) :
- services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetRequiredService>().Value.JsonSerializerOptions })));
+ services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
+ services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
}
}
}
diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
index 038676627..dcbca957b 100644
--- a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
+++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
@@ -1,32 +1,47 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
-using ModelContextProtocol.Tests.Utils;
-using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Tests.Configuration;
-public class McpServerJsonSerializerOptionsTests : ClientServerTestBase
+public class McpServerJsonSerializerOptionsTests
{
- public McpServerJsonSerializerOptionsTests(ITestOutputHelper testOutputHelper)
- : base(testOutputHelper)
+ [Fact]
+ public void McpServerOptions_JsonSerializerOptions_DefaultsToNull()
{
+ // Arrange & Act
+ var options = new McpServerOptions();
+
+ // Assert
+ Assert.Null(options.JsonSerializerOptions);
}
- private class SpecialNumbers
+ [Fact]
+ public void McpServerOptions_JsonSerializerOptions_CanBeSet()
{
- public double PositiveInfinity { get; set; }
- public double NegativeInfinity { get; set; }
- public double NotANumber { get; set; }
+ // Arrange
+ var customOptions = new JsonSerializerOptions
+ {
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
+ };
+ var options = new McpServerOptions();
+
+ // Act
+ options.JsonSerializerOptions = customOptions;
+
+ // Assert
+ Assert.NotNull(options.JsonSerializerOptions);
+ Assert.Equal(JsonNumberHandling.AllowNamedFloatingPointLiterals, options.JsonSerializerOptions.NumberHandling);
}
- protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
+ [Fact]
+ public void WithTools_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
{
- // Configure server-wide JsonSerializerOptions to allow named floating point literals
+ // Arrange
+ var services = new ServiceCollection();
var customOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
{
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
@@ -37,58 +52,22 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer
options.JsonSerializerOptions = customOptions;
});
- // Register a tool that will use the server-wide JsonSerializerOptions
- // The null serializerOptions parameter should cause it to use McpServerOptions.JsonSerializerOptions
- services.AddSingleton(sp =>
- {
- var serverOptions = sp.GetRequiredService>().Value;
- return McpServerTool.Create(
- () => new SpecialNumbers
- {
- PositiveInfinity = double.PositiveInfinity,
- NegativeInfinity = double.NegativeInfinity,
- NotANumber = double.NaN
- },
- new McpServerToolCreateOptions
- {
- Name = "GetSpecialNumbers",
- Description = "Returns special floating point values",
- UseStructuredContent = true,
- SerializerOptions = serverOptions.JsonSerializerOptions,
- Services = sp
- });
- });
- }
+ var builder = services.AddMcpServer();
- [Fact]
- public async Task ServerWide_JsonSerializerOptions_Applied_To_Tools()
- {
- // Arrange
- McpClient client = await CreateMcpClientForServer();
+ // Act - WithTools should pick up the server-wide options
+ builder.WithTools();
+ var serviceProvider = services.BuildServiceProvider();
- // Act
- IList tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
- CallToolResult result = await client.CallToolAsync("GetSpecialNumbers", cancellationToken: TestContext.Current.CancellationToken);
-
- // Assert
- Assert.NotNull(tools);
+ // Assert - Verify the tool was registered
+ var tools = serviceProvider.GetServices().ToList();
Assert.Single(tools);
- Assert.Equal("GetSpecialNumbers", tools[0].Name);
-
- // Verify the result contains structured content with special numbers
- Assert.NotNull(result);
- Assert.NotNull(result.StructuredContent);
-
- var structuredContent = JsonSerializer.Deserialize(
- result.StructuredContent.ToString(),
- new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
- {
- NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
- });
+ Assert.Equal("TestTool", tools[0].ProtocolTool.Name);
+ }
- Assert.NotNull(structuredContent);
- Assert.True(double.IsPositiveInfinity(structuredContent.PositiveInfinity));
- Assert.True(double.IsNegativeInfinity(structuredContent.NegativeInfinity));
- Assert.True(double.IsNaN(structuredContent.NotANumber));
+ [McpServerToolType]
+ private class TestTools
+ {
+ [McpServerTool]
+ public static string TestTool() => "test";
}
}
From f7b9deec2601e624e7621987777aae9bc4eb40ba Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 14:09:09 +0000
Subject: [PATCH 04/10] Add usage documentation and example for JSON
serialization configuration
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
USAGE_EXAMPLE.md | 87 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 USAGE_EXAMPLE.md
diff --git a/USAGE_EXAMPLE.md b/USAGE_EXAMPLE.md
new file mode 100644
index 000000000..88c301846
--- /dev/null
+++ b/USAGE_EXAMPLE.md
@@ -0,0 +1,87 @@
+# JSON Serialization Configuration for MCP Server
+
+This document demonstrates how to configure JSON serialization options for your MCP server to handle special cases like `double.Infinity` or `NaN` values.
+
+## Problem
+
+By default, JSON serialization in .NET doesn't support special floating-point values like positive/negative infinity and NaN. When a tool returns such values, you would get an error:
+
+```
+System.ArgumentException: .NET number values such as positive and negative infinity cannot be written as valid JSON.
+To make it work when using 'JsonSerializer', consider specifying 'JsonNumberHandling.AllowNamedFloatingPointLiterals'
+```
+
+## Solution
+
+Configure server-wide JSON serialization options when setting up your MCP server:
+
+```csharp
+using Microsoft.Extensions.DependencyInjection;
+using ModelContextProtocol.Server;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Configure server-wide JSON serialization options
+builder.Services.AddMcpServer(options =>
+{
+ options.JsonSerializerOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
+ {
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
+ };
+})
+.WithTools();
+
+var app = builder.Build();
+app.Run();
+
+[McpServerToolType]
+public class MyTools
+{
+ [McpServerTool]
+ public static double[] GetSpecialNumbers()
+ {
+ // These values will now serialize correctly as "Infinity", "-Infinity", and "NaN"
+ return new[] { double.PositiveInfinity, double.NegativeInfinity, double.NaN };
+ }
+}
+```
+
+## How It Works
+
+1. The `JsonSerializerOptions` property on `McpServerOptions` provides server-wide default serialization settings
+2. All tools, prompts, and resources registered via `WithTools*`, `WithPrompts*`, and `WithResources*` will use these options by default
+3. Individual registrations can still override with their own specific options if needed
+
+## Override for Specific Tools
+
+If you need different serialization options for specific tools, you can still provide them explicitly:
+
+```csharp
+var customOptions = new JsonSerializerOptions
+{
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+};
+
+builder.Services.AddMcpServer(options =>
+{
+ options.JsonSerializerOptions = McpJsonUtilities.DefaultOptions; // Default for most tools
+})
+.WithTools() // Uses server-wide options
+.WithTools(customOptions); // Uses custom options
+```
+
+## Additional Configuration Options
+
+You can configure other JSON serialization settings as needed:
+
+```csharp
+options.JsonSerializerOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
+{
+ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
+ PropertyNameCaseInsensitive = true,
+ AllowTrailingCommas = true,
+ WriteIndented = true // For debugging
+};
+```
From eca0585e3ea057f7a0fdbf1034390a0f1bb3d359 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 14:51:29 +0000
Subject: [PATCH 05/10] Improve test to verify JsonSerializerOptions are
actually used by checking schema naming policy
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../McpServerJsonSerializerOptionsTests.cs | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
index dcbca957b..cdcb7b3e4 100644
--- a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
+++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
@@ -44,7 +44,7 @@ public void WithTools_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
var services = new ServiceCollection();
var customOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
{
- NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
services.Configure(options =>
@@ -54,20 +54,30 @@ public void WithTools_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
var builder = services.AddMcpServer();
- // Act - WithTools should pick up the server-wide options
+ // Act - WithTools should pick up the server-wide options with snake_case naming policy
builder.WithTools();
var serviceProvider = services.BuildServiceProvider();
- // Assert - Verify the tool was registered
+ // Assert - Verify the tool schema uses snake_case property naming
var tools = serviceProvider.GetServices().ToList();
Assert.Single(tools);
- Assert.Equal("TestTool", tools[0].ProtocolTool.Name);
+
+ var tool = tools[0];
+ Assert.Equal("ToolWithParameters", tool.ProtocolTool.Name);
+
+ // Check that the input schema uses snake_case for property names
+ var inputSchema = tool.ProtocolTool.InputSchema;
+
+ // The schema should have a "properties" object with snake_case property names
+ var propertiesElement = inputSchema.GetProperty("properties");
+ Assert.True(propertiesElement.TryGetProperty("my_parameter", out _), "Schema should have 'my_parameter' property (snake_case)");
+ Assert.False(propertiesElement.TryGetProperty("MyParameter", out _), "Schema should not have 'MyParameter' property (PascalCase)");
}
[McpServerToolType]
private class TestTools
{
[McpServerTool]
- public static string TestTool() => "test";
+ public static string ToolWithParameters(string myParameter) => myParameter;
}
}
From 542fa910cc5f2578fa3175364efa08120516bf74 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 14:54:15 +0000
Subject: [PATCH 06/10] Remove USAGE_EXAMPLE.md file
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
USAGE_EXAMPLE.md | 87 ------------------------------------------------
1 file changed, 87 deletions(-)
delete mode 100644 USAGE_EXAMPLE.md
diff --git a/USAGE_EXAMPLE.md b/USAGE_EXAMPLE.md
deleted file mode 100644
index 88c301846..000000000
--- a/USAGE_EXAMPLE.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# JSON Serialization Configuration for MCP Server
-
-This document demonstrates how to configure JSON serialization options for your MCP server to handle special cases like `double.Infinity` or `NaN` values.
-
-## Problem
-
-By default, JSON serialization in .NET doesn't support special floating-point values like positive/negative infinity and NaN. When a tool returns such values, you would get an error:
-
-```
-System.ArgumentException: .NET number values such as positive and negative infinity cannot be written as valid JSON.
-To make it work when using 'JsonSerializer', consider specifying 'JsonNumberHandling.AllowNamedFloatingPointLiterals'
-```
-
-## Solution
-
-Configure server-wide JSON serialization options when setting up your MCP server:
-
-```csharp
-using Microsoft.Extensions.DependencyInjection;
-using ModelContextProtocol.Server;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-var builder = WebApplication.CreateBuilder(args);
-
-// Configure server-wide JSON serialization options
-builder.Services.AddMcpServer(options =>
-{
- options.JsonSerializerOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
- {
- NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
- };
-})
-.WithTools();
-
-var app = builder.Build();
-app.Run();
-
-[McpServerToolType]
-public class MyTools
-{
- [McpServerTool]
- public static double[] GetSpecialNumbers()
- {
- // These values will now serialize correctly as "Infinity", "-Infinity", and "NaN"
- return new[] { double.PositiveInfinity, double.NegativeInfinity, double.NaN };
- }
-}
-```
-
-## How It Works
-
-1. The `JsonSerializerOptions` property on `McpServerOptions` provides server-wide default serialization settings
-2. All tools, prompts, and resources registered via `WithTools*`, `WithPrompts*`, and `WithResources*` will use these options by default
-3. Individual registrations can still override with their own specific options if needed
-
-## Override for Specific Tools
-
-If you need different serialization options for specific tools, you can still provide them explicitly:
-
-```csharp
-var customOptions = new JsonSerializerOptions
-{
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
-};
-
-builder.Services.AddMcpServer(options =>
-{
- options.JsonSerializerOptions = McpJsonUtilities.DefaultOptions; // Default for most tools
-})
-.WithTools() // Uses server-wide options
-.WithTools(customOptions); // Uses custom options
-```
-
-## Additional Configuration Options
-
-You can configure other JSON serialization settings as needed:
-
-```csharp
-options.JsonSerializerOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
-{
- NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
- PropertyNameCaseInsensitive = true,
- AllowTrailingCommas = true,
- WriteIndented = true // For debugging
-};
-```
From dfcd1ecbc774265661b12aaae2157a82b7737d18 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 15:02:08 +0000
Subject: [PATCH 07/10] Add tests for prompts and resources using server-wide
JsonSerializerOptions
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../McpServerJsonSerializerOptionsTests.cs | 81 +++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
index cdcb7b3e4..d00c7f745 100644
--- a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
+++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
@@ -1,3 +1,4 @@
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Protocol;
@@ -74,10 +75,90 @@ public void WithTools_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
Assert.False(propertiesElement.TryGetProperty("MyParameter", out _), "Schema should not have 'MyParameter' property (PascalCase)");
}
+ [Fact]
+ public void WithPrompts_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var customOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ };
+
+ services.Configure(options =>
+ {
+ options.JsonSerializerOptions = customOptions;
+ });
+
+ var builder = services.AddMcpServer();
+
+ // Act - WithPrompts should pick up the server-wide options with snake_case naming policy
+ builder.WithPrompts();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert - Verify the prompt schema uses snake_case property naming
+ var prompts = serviceProvider.GetServices().ToList();
+ Assert.Single(prompts);
+
+ var prompt = prompts[0];
+ Assert.Equal("PromptWithParameters", prompt.ProtocolPrompt.Name);
+
+ // Check that the arguments schema uses snake_case for property names
+ var arguments = prompt.ProtocolPrompt.Arguments;
+ Assert.NotNull(arguments);
+ Assert.Single(arguments);
+ Assert.Equal("my_argument", arguments[0].Name);
+ }
+
+ [Fact]
+ public void WithResources_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var customOptions = new JsonSerializerOptions(McpJsonUtilities.DefaultOptions)
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ };
+
+ services.Configure(options =>
+ {
+ options.JsonSerializerOptions = customOptions;
+ });
+
+ var builder = services.AddMcpServer();
+
+ // Act - WithResources should pick up the server-wide options with snake_case naming policy
+ builder.WithResources();
+ var serviceProvider = services.BuildServiceProvider();
+
+ // Assert - Verify the resource was registered (resources don't expose schema in the same way)
+ var resources = serviceProvider.GetServices().ToList();
+ Assert.Single(resources);
+
+ var resource = resources[0];
+ Assert.Equal("resource://test/{myParameter}", resource.ProtocolResourceTemplate.UriTemplate);
+ }
+
[McpServerToolType]
private class TestTools
{
[McpServerTool]
public static string ToolWithParameters(string myParameter) => myParameter;
}
+
+ [McpServerPromptType]
+ private class TestPrompts
+ {
+ [McpServerPrompt]
+ public static ChatMessage PromptWithParameters(string myArgument) =>
+ new(ChatRole.User, $"Prompt with: {myArgument}");
+ }
+
+ [McpServerResourceType]
+ private class TestResources
+ {
+ [McpServerResource(UriTemplate = "resource://test/{myParameter}")]
+ public static string ResourceWithParameters(string myParameter) =>
+ $"Resource content: {myParameter}";
+ }
}
From e62aae87f0293ef5bae73ce11f1a5fc835e7cb6e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Oct 2025 16:38:27 +0000
Subject: [PATCH 08/10] Add SchemaCreateOptions property to McpServerOptions
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../Server/McpServerOptions.cs | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs
index f3e9dcc68..3f323bbe3 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs
@@ -81,6 +81,21 @@ public sealed class McpServerOptions
///
public JsonSerializerOptions? JsonSerializerOptions { get; set; }
+ ///
+ /// Gets or sets the default JSON schema creation options to use for tools, prompts, and resources.
+ ///
+ ///
+ ///
+ /// This property provides server-wide default schema creation settings that will be used
+ /// by all tools, prompts, and resources unless they explicitly specify their own
+ /// during registration.
+ ///
+ ///
+ /// If not set, defaults to .
+ ///
+ ///
+ public Microsoft.Extensions.AI.AIJsonSchemaCreateOptions? SchemaCreateOptions { get; set; }
+
///
/// Gets or sets whether to create a new service provider scope for each handled request.
///
From d56b8e541054f1e037cb48dd6fd7a83669823d4a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 31 Oct 2025 06:08:30 +0000
Subject: [PATCH 09/10] Add AIJsonSchemaCreateOptions parameter to all
WithTools/Prompts/Resources methods
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../McpServerBuilderExtensions.cs | 88 ++++++++++++-------
1 file changed, 58 insertions(+), 30 deletions(-)
diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
index 57ba5d854..fde2a8991 100644
--- a/src/ModelContextProtocol/McpServerBuilderExtensions.cs
+++ b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
@@ -1,3 +1,4 @@
+using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -24,6 +25,7 @@ public static partial class McpServerBuilderExtensions
/// The tool type.
/// The builder instance.
/// The serializer options governing tool parameter marshalling.
+ /// The JSON schema creation options governing tool schema generation.
/// The builder provided in .
/// is .
///
@@ -36,7 +38,8 @@ public static partial class McpServerBuilderExtensions
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TToolType>(
this IMcpServerBuilder builder,
- JsonSerializerOptions? serializerOptions = null)
+ JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
@@ -45,8 +48,8 @@ public static partial class McpServerBuilderExtensions
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
- services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
+ services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions, SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
+ services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions, SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
}
}
@@ -58,6 +61,7 @@ public static partial class McpServerBuilderExtensions
/// The builder instance.
/// The target instance from which the tools should be sourced.
/// The serializer options governing tool parameter marshalling.
+ /// The JSON schema creation options governing tool schema generation.
/// The builder provided in .
/// is .
///
@@ -76,7 +80,8 @@ public static partial class McpServerBuilderExtensions
DynamicallyAccessedMemberTypes.NonPublicMethods)] TToolType>(
this IMcpServerBuilder builder,
TToolType target,
- JsonSerializerOptions? serializerOptions = null)
+ JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(target);
@@ -93,7 +98,7 @@ public static partial class McpServerBuilderExtensions
builder.Services.AddSingleton(services => McpServerTool.Create(
toolMethod,
toolMethod.IsStatic ? null : target,
- new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }));
+ new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }));
}
}
@@ -126,6 +131,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
/// The builder instance.
/// Types with -attributed methods to add as tools to the server.
/// The serializer options governing tool parameter marshalling.
+ /// The JSON schema creation options governing tool schema generation.
/// The builder provided in .
/// is .
/// is .
@@ -135,7 +141,8 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
///
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null)
+ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(toolTypes);
@@ -149,8 +156,8 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
- services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
+ services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
+ services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
}
}
}
@@ -164,6 +171,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
///
/// The builder instance.
/// The serializer options governing tool parameter marshalling.
+ /// The JSON schema creation options governing tool schema generation.
/// The assembly to load the types from. If , the calling assembly will be used.
/// The builder provided in .
/// is .
@@ -188,7 +196,8 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume
///
///
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null)
+ public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
@@ -198,7 +207,8 @@ public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder bui
from t in toolAssembly.GetTypes()
where t.GetCustomAttribute() is not null
select t,
- serializerOptions);
+ serializerOptions,
+ schemaCreateOptions);
}
#endregion
@@ -211,6 +221,7 @@ where t.GetCustomAttribute() is not null
/// The prompt type.
/// The builder instance.
/// The serializer options governing prompt parameter marshalling.
+ /// The JSON schema creation options governing prompt schema generation.
/// The builder provided in .
/// is .
///
@@ -223,7 +234,8 @@ where t.GetCustomAttribute() is not null
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TPromptType>(
this IMcpServerBuilder builder,
- JsonSerializerOptions? serializerOptions = null)
+ JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
@@ -232,8 +244,8 @@ where t.GetCustomAttribute() is not null
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
- services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
- services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
+ services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
+ services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
}
}
@@ -245,6 +257,7 @@ where t.GetCustomAttribute() is not null
/// The builder instance.
/// The target instance from which the prompts should be sourced.
/// The serializer options governing prompt parameter marshalling.
+ /// The JSON schema creation options governing prompt schema generation.
/// The builder provided in .
/// is .
///
@@ -263,7 +276,8 @@ where t.GetCustomAttribute() is not null
DynamicallyAccessedMemberTypes.NonPublicMethods)] TPromptType>(
this IMcpServerBuilder builder,
TPromptType target,
- JsonSerializerOptions? serializerOptions = null)
+ JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(target);
@@ -277,7 +291,7 @@ where t.GetCustomAttribute() is not null
{
if (promptMethod.GetCustomAttribute() is not null)
{
- builder.Services.AddSingleton(services => McpServerPrompt.Create(promptMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }));
+ builder.Services.AddSingleton(services => McpServerPrompt.Create(promptMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }));
}
}
@@ -310,6 +324,7 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
/// The builder instance.
/// Types with marked methods to add as prompts to the server.
/// The serializer options governing prompt parameter marshalling.
+ /// The JSON schema creation options governing prompt schema generation.
/// The builder provided in .
/// is .
/// is .
@@ -319,7 +334,8 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
/// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
///
[RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnumerable promptTypes, JsonSerializerOptions? serializerOptions = null)
+ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnumerable promptTypes, JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(promptTypes);
@@ -333,8 +349,8 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
- services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
- services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
+ services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
+ services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
}
}
}
@@ -348,6 +364,7 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
///
/// The builder instance.
/// The serializer options governing prompt parameter marshalling.
+ /// The JSON schema creation options governing prompt schema generation.
/// The assembly to load the types from. If , the calling assembly will be used.
/// The builder provided in .
/// is .
@@ -372,7 +389,8 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu
///
///
[RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder builder, Assembly? promptAssembly = null, JsonSerializerOptions? serializerOptions = null)
+ public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder builder, Assembly? promptAssembly = null, JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
@@ -382,7 +400,8 @@ public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder b
from t in promptAssembly.GetTypes()
where t.GetCustomAttribute() is not null
select t,
- serializerOptions);
+ serializerOptions,
+ schemaCreateOptions);
}
#endregion
@@ -395,6 +414,7 @@ where t.GetCustomAttribute() is not null
/// The resource type.
/// The builder instance.
/// The serializer options governing resource parameter marshalling.
+ /// The JSON schema creation options governing resource schema generation.
/// The builder provided in .
/// is .
///
@@ -407,7 +427,8 @@ where t.GetCustomAttribute() is not null
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TResourceType>(
this IMcpServerBuilder builder,
- JsonSerializerOptions? serializerOptions = null)
+ JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
@@ -416,8 +437,8 @@ where t.GetCustomAttribute() is not null
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ?
- services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
- services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
+ services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
+ services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
}
}
@@ -429,6 +450,7 @@ where t.GetCustomAttribute() is not null
/// The builder instance.
/// The target instance from which the prompts should be sourced.
/// The serializer options governing resource parameter marshalling.
+ /// The JSON schema creation options governing resource schema generation.
/// The builder provided in .
/// is .
///
@@ -447,7 +469,8 @@ where t.GetCustomAttribute() is not null
DynamicallyAccessedMemberTypes.NonPublicMethods)] TResourceType>(
this IMcpServerBuilder builder,
TResourceType target,
- JsonSerializerOptions? serializerOptions = null)
+ JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(target);
@@ -461,7 +484,7 @@ where t.GetCustomAttribute() is not null
{
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
- builder.Services.AddSingleton(services => McpServerResource.Create(resourceTemplateMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }));
+ builder.Services.AddSingleton(services => McpServerResource.Create(resourceTemplateMethod, target, new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }));
}
}
@@ -494,6 +517,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
/// The builder instance.
/// Types with marked methods to add as resources to the server.
/// The serializer options governing resource parameter marshalling.
+ /// The JSON schema creation options governing resource schema generation.
/// The builder provided in .
/// is .
/// is .
@@ -503,7 +527,8 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
/// instance for each. For instance methods, an instance will be constructed for each invocation of the resource.
///
[RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable resourceTemplateTypes, JsonSerializerOptions? serializerOptions = null)
+ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable resourceTemplateTypes, JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
Throw.IfNull(resourceTemplateTypes);
@@ -517,8 +542,8 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
if (resourceTemplateMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ?
- services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions }) :
- services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions })));
+ services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
+ services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions , SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
}
}
}
@@ -532,6 +557,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
///
/// The builder instance.
/// The serializer options governing resource parameter marshalling.
+ /// The JSON schema creation options governing resource schema generation.
/// The assembly to load the types from. If , the calling assembly will be used.
/// The builder provided in .
/// is .
@@ -556,7 +582,8 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE
///
///
[RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)]
- public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder builder, Assembly? resourceAssembly = null, JsonSerializerOptions? serializerOptions = null)
+ public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder builder, Assembly? resourceAssembly = null, JsonSerializerOptions? serializerOptions = null,
+ AIJsonSchemaCreateOptions? schemaCreateOptions = null)
{
Throw.IfNull(builder);
@@ -566,7 +593,8 @@ public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder
from t in resourceAssembly.GetTypes()
where t.GetCustomAttribute() is not null
select t,
- serializerOptions);
+ serializerOptions,
+ schemaCreateOptions);
}
#endregion
From 750708180960106693a7a928f5e10722b78ee946 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 31 Oct 2025 14:31:50 +0000
Subject: [PATCH 10/10] Fix stack overflow by using McpServerDefaultOptions
instead of resolving IOptions during tool creation
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../McpServerBuilderExtensions.cs | 32 +++++++++++++++++--
.../McpServerDefaultOptions.cs | 21 ++++++++++++
.../McpServerServiceCollectionExtensions.cs | 13 ++++++++
.../McpServerJsonSerializerOptionsTests.cs | 12 ++-----
4 files changed, 67 insertions(+), 11 deletions(-)
create mode 100644 src/ModelContextProtocol/McpServerDefaultOptions.cs
diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
index fde2a8991..693f74f65 100644
--- a/src/ModelContextProtocol/McpServerBuilderExtensions.cs
+++ b/src/ModelContextProtocol/McpServerBuilderExtensions.cs
@@ -48,8 +48,36 @@ public static partial class McpServerBuilderExtensions
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
- services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions, SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions }) :
- services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions ?? services.GetService>()?.Value.JsonSerializerOptions, SchemaCreateOptions = schemaCreateOptions ?? services.GetService>()?.Value.SchemaCreateOptions })));
+ services =>
+ {
+ var effectiveSerializerOptions = serializerOptions;
+ var effectiveSchemaCreateOptions = schemaCreateOptions;
+
+ // Try to get server-wide defaults if not explicitly provided
+ if (effectiveSerializerOptions is null || effectiveSchemaCreateOptions is null)
+ {
+ var defaultOptions = services.GetService();
+ effectiveSerializerOptions ??= defaultOptions?.JsonSerializerOptions;
+ effectiveSchemaCreateOptions ??= defaultOptions?.SchemaCreateOptions;
+ }
+
+ return McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = effectiveSerializerOptions, SchemaCreateOptions = effectiveSchemaCreateOptions });
+ } :
+ services =>
+ {
+ var effectiveSerializerOptions = serializerOptions;
+ var effectiveSchemaCreateOptions = schemaCreateOptions;
+
+ // Try to get server-wide defaults if not explicitly provided
+ if (effectiveSerializerOptions is null || effectiveSchemaCreateOptions is null)
+ {
+ var defaultOptions = services.GetService();
+ effectiveSerializerOptions ??= defaultOptions?.JsonSerializerOptions;
+ effectiveSchemaCreateOptions ??= defaultOptions?.SchemaCreateOptions;
+ }
+
+ return McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = effectiveSerializerOptions, SchemaCreateOptions = effectiveSchemaCreateOptions });
+ }));
}
}
diff --git a/src/ModelContextProtocol/McpServerDefaultOptions.cs b/src/ModelContextProtocol/McpServerDefaultOptions.cs
new file mode 100644
index 000000000..e1d3a1e22
--- /dev/null
+++ b/src/ModelContextProtocol/McpServerDefaultOptions.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.AI;
+using System.Text.Json;
+
+namespace ModelContextProtocol.Server;
+
+///
+/// Holds default options for MCP server primitives (tools, prompts, resources).
+/// This is separate from McpServerOptions to avoid circular dependencies during service resolution.
+///
+internal sealed class McpServerDefaultOptions
+{
+ ///
+ /// Gets or sets the default JSON serializer options.
+ ///
+ public JsonSerializerOptions? JsonSerializerOptions { get; set; }
+
+ ///
+ /// Gets or sets the default JSON schema creation options.
+ ///
+ public AIJsonSchemaCreateOptions? SchemaCreateOptions { get; set; }
+}
diff --git a/src/ModelContextProtocol/McpServerServiceCollectionExtensions.cs b/src/ModelContextProtocol/McpServerServiceCollectionExtensions.cs
index d0072002c..9910ce84e 100644
--- a/src/ModelContextProtocol/McpServerServiceCollectionExtensions.cs
+++ b/src/ModelContextProtocol/McpServerServiceCollectionExtensions.cs
@@ -21,10 +21,23 @@ public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, A
{
services.AddOptions();
services.TryAddEnumerable(ServiceDescriptor.Transient, McpServerOptionsSetup>());
+
+ // Capture default options from the configuration callback to avoid circular dependencies
+ // when resolving IOptions from within tool/prompt/resource factories
+ var defaultOptions = new McpServerDefaultOptions();
if (configureOptions is not null)
{
+ var tempOptions = new McpServerOptions();
+ configureOptions(tempOptions);
+ defaultOptions.JsonSerializerOptions = tempOptions.JsonSerializerOptions;
+ defaultOptions.SchemaCreateOptions = tempOptions.SchemaCreateOptions;
+
services.Configure(configureOptions);
}
+
+ // Register the default options as a singleton that can be safely resolved
+ // without circular dependencies
+ services.TryAddSingleton(defaultOptions);
return new DefaultMcpServerBuilder(services);
}
diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
index d00c7f745..e2cd323e0 100644
--- a/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
+++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerJsonSerializerOptionsTests.cs
@@ -48,13 +48,11 @@ public void WithTools_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
- services.Configure(options =>
+ var builder = services.AddMcpServer(options =>
{
options.JsonSerializerOptions = customOptions;
});
- var builder = services.AddMcpServer();
-
// Act - WithTools should pick up the server-wide options with snake_case naming policy
builder.WithTools();
var serviceProvider = services.BuildServiceProvider();
@@ -85,13 +83,11 @@ public void WithPrompts_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
- services.Configure(options =>
+ var builder = services.AddMcpServer(options =>
{
options.JsonSerializerOptions = customOptions;
});
- var builder = services.AddMcpServer();
-
// Act - WithPrompts should pick up the server-wide options with snake_case naming policy
builder.WithPrompts();
var serviceProvider = services.BuildServiceProvider();
@@ -120,13 +116,11 @@ public void WithResources_UsesServerWideOptions_WhenNoExplicitOptionsProvided()
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
- services.Configure(options =>
+ var builder = services.AddMcpServer(options =>
{
options.JsonSerializerOptions = customOptions;
});
- var builder = services.AddMcpServer();
-
// Act - WithResources should pick up the server-wide options with snake_case naming policy
builder.WithResources();
var serviceProvider = services.BuildServiceProvider();