Update Microsoft.Extensions.AI version & expose JSON configuration on McpServerResourceCreateOptions (#511)
* Update Microsoft.Extensions.AI version * Update Microsoft.Extensions.AI.OpenAI * Fix failing tests; add JSON configuration options to McpServerResourceCreateOptions. * Replace temporary code with `AIFunction.ReturnJsonSchema` --------- Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
This commit is contained in:
parent
1e8fb04037
commit
28d65470f4
9 changed files with 45 additions and 35 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
<System9Version>9.0.5</System9Version>
|
<System9Version>9.0.5</System9Version>
|
||||||
<System10Version>10.0.0-preview.4.25258.110</System10Version>
|
<System10Version>10.0.0-preview.4.25258.110</System10Version>
|
||||||
<MicrosoftExtensionsAIVersion>9.5.0</MicrosoftExtensionsAIVersion>
|
<MicrosoftExtensionsAIVersion>9.6.0</MicrosoftExtensionsAIVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Product dependencies netstandard -->
|
<!-- Product dependencies netstandard -->
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageVersion>
|
</PackageVersion>
|
||||||
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
|
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.5.0-preview.1.25265.7" />
|
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.6.0-preview.1.25310.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(System9Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(System9Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(System9Version)" />
|
||||||
|
|
|
||||||
|
|
@ -85,30 +85,6 @@ public static partial class McpJsonUtilities
|
||||||
return false; // No type keyword found.
|
return false; // No type keyword found.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static JsonElement? GetReturnSchema(this AIFunction function, AIJsonSchemaCreateOptions? schemaCreateOptions)
|
|
||||||
{
|
|
||||||
// TODO replace with https://github.com/dotnet/extensions/pull/6447 once merged.
|
|
||||||
if (function.UnderlyingMethod?.ReturnType is not Type returnType)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (returnType == typeof(void) || returnType == typeof(Task) || returnType == typeof(ValueTask))
|
|
||||||
{
|
|
||||||
// Do not report an output schema for void or Task methods.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() is Type genericTypeDef &&
|
|
||||||
(genericTypeDef == typeof(Task<>) || genericTypeDef == typeof(ValueTask<>)))
|
|
||||||
{
|
|
||||||
// Extract the real type from Task<T> or ValueTask<T> if applicable.
|
|
||||||
returnType = returnType.GetGenericArguments()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return AIJsonUtilities.CreateJsonSchema(returnType, serializerOptions: function.JsonSerializerOptions, inferenceOptions: schemaCreateOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep in sync with CreateDefaultOptions above.
|
// Keep in sync with CreateDefaultOptions above.
|
||||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
|
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
|
@ -157,6 +133,7 @@ public static partial class McpJsonUtilities
|
||||||
[JsonSerializable(typeof(SubscribeRequestParams))]
|
[JsonSerializable(typeof(SubscribeRequestParams))]
|
||||||
[JsonSerializable(typeof(UnsubscribeRequestParams))]
|
[JsonSerializable(typeof(UnsubscribeRequestParams))]
|
||||||
[JsonSerializable(typeof(IReadOnlyDictionary<string, object>))]
|
[JsonSerializable(typeof(IReadOnlyDictionary<string, object>))]
|
||||||
|
[JsonSerializable(typeof(PromptMessage[]))]
|
||||||
|
|
||||||
// Primitive types for use in consuming AIFunctions
|
// Primitive types for use in consuming AIFunctions
|
||||||
[JsonSerializable(typeof(string))]
|
[JsonSerializable(typeof(string))]
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||||
Description = options?.Description,
|
Description = options?.Description,
|
||||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||||
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
|
JsonSchemaCreateOptions = options?.SchemaCreateOptions,
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
|
||||||
|
|
@ -151,7 +152,6 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
JsonSchemaCreateOptions = options?.SchemaCreateOptions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Creates an <see cref="McpServerPrompt"/> that wraps the specified <see cref="AIFunction"/>.</summary>
|
/// <summary>Creates an <see cref="McpServerPrompt"/> that wraps the specified <see cref="AIFunction"/>.</summary>
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,8 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
Name = options?.Name ?? method.GetCustomAttribute<McpServerResourceAttribute>()?.Name,
|
Name = options?.Name ?? method.GetCustomAttribute<McpServerResourceAttribute>()?.Name,
|
||||||
Description = options?.Description,
|
Description = options?.Description,
|
||||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||||
SerializerOptions = McpJsonUtilities.DefaultOptions,
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
|
JsonSchemaCreateOptions = options?.SchemaCreateOptions,
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<ReadResourceRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<ReadResourceRequestParams>))
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
Description = options?.Description,
|
Description = options?.Description,
|
||||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||||
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
|
JsonSchemaCreateOptions = options?.SchemaCreateOptions,
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
||||||
|
|
@ -166,7 +167,6 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
JsonSchemaCreateOptions = options?.SchemaCreateOptions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Creates an <see cref="McpServerTool"/> that wraps the specified <see cref="AIFunction"/>.</summary>
|
/// <summary>Creates an <see cref="McpServerTool"/> that wraps the specified <see cref="AIFunction"/>.</summary>
|
||||||
|
|
@ -366,7 +366,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (function.GetReturnSchema(toolCreateOptions?.SchemaCreateOptions) is not JsonElement outputSchema)
|
if (function.ReturnJsonSchema is not JsonElement outputSchema)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
using Microsoft.Extensions.AI;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Server;
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -60,6 +62,22 @@ public sealed class McpServerResourceCreateOptions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? MimeType { get; set; }
|
public string? MimeType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the JSON serializer options to use when marshalling data to/from JSON.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to <see cref="McpJsonUtilities.DefaultOptions"/> if left unspecified.
|
||||||
|
/// </remarks>
|
||||||
|
public JsonSerializerOptions? SerializerOptions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the JSON schema options when creating <see cref="AIFunction"/> from a method.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to <see cref="AIJsonSchemaCreateOptions.Default"/> if left unspecified.
|
||||||
|
/// </remarks>
|
||||||
|
public AIJsonSchemaCreateOptions? SchemaCreateOptions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a shallow clone of the current <see cref="McpServerResourceCreateOptions"/> instance.
|
/// Creates a shallow clone of the current <see cref="McpServerResourceCreateOptions"/> instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -71,5 +89,7 @@ public sealed class McpServerResourceCreateOptions
|
||||||
Name = Name,
|
Name = Name,
|
||||||
Description = Description,
|
Description = Description,
|
||||||
MimeType = MimeType,
|
MimeType = MimeType,
|
||||||
|
SerializerOptions = SerializerOptions,
|
||||||
|
SchemaCreateOptions = SchemaCreateOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ using ModelContextProtocol.Server;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests.Server;
|
namespace ModelContextProtocol.Tests.Server;
|
||||||
|
|
||||||
|
|
@ -324,6 +326,11 @@ public class McpServerPromptTests
|
||||||
{
|
{
|
||||||
TransformSchemaNode = (context, node) =>
|
TransformSchemaNode = (context, node) =>
|
||||||
{
|
{
|
||||||
|
if (node.GetValueKind() is not JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
node = new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
node["description"] = "1234";
|
node["description"] = "1234";
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -451,7 +451,7 @@ public partial class McpServerResourceTests
|
||||||
{
|
{
|
||||||
Assert.Same(mockServer.Object, server);
|
Assert.Same(mockServer.Object, server);
|
||||||
return new TextResourceContents() { Text = "hello" };
|
return new TextResourceContents() { Text = "hello" };
|
||||||
}, new() { Name = "Test" });
|
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
|
||||||
var result = await resource.ReadAsync(
|
var result = await resource.ReadAsync(
|
||||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
||||||
TestContext.Current.CancellationToken);
|
TestContext.Current.CancellationToken);
|
||||||
|
|
@ -507,7 +507,7 @@ public partial class McpServerResourceTests
|
||||||
{
|
{
|
||||||
Assert.Same(mockServer.Object, server);
|
Assert.Same(mockServer.Object, server);
|
||||||
return new List<string>() { "42", "43" };
|
return new List<string>() { "42", "43" };
|
||||||
}, new() { Name = "Test" });
|
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
|
||||||
var result = await resource.ReadAsync(
|
var result = await resource.ReadAsync(
|
||||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
||||||
TestContext.Current.CancellationToken);
|
TestContext.Current.CancellationToken);
|
||||||
|
|
@ -547,7 +547,7 @@ public partial class McpServerResourceTests
|
||||||
new TextContent("hello!"),
|
new TextContent("hello!"),
|
||||||
new DataContent(new byte[] { 4, 5, 6 }, "application/json"),
|
new DataContent(new byte[] { 4, 5, 6 }, "application/json"),
|
||||||
};
|
};
|
||||||
}, new() { Name = "Test" });
|
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
|
||||||
var result = await resource.ReadAsync(
|
var result = await resource.ReadAsync(
|
||||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
||||||
TestContext.Current.CancellationToken);
|
TestContext.Current.CancellationToken);
|
||||||
|
|
@ -575,5 +575,8 @@ public partial class McpServerResourceTests
|
||||||
|
|
||||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||||
[JsonSerializable(typeof(DisposableResourceType))]
|
[JsonSerializable(typeof(DisposableResourceType))]
|
||||||
|
[JsonSerializable(typeof(List<AIContent>))]
|
||||||
|
[JsonSerializable(typeof(List<string>))]
|
||||||
|
[JsonSerializable(typeof(TextResourceContents))]
|
||||||
partial class JsonContext6 : JsonSerializerContext;
|
partial class JsonContext6 : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ public partial class McpServerToolTests
|
||||||
new DataContent("data:image/png;base64,1234"),
|
new DataContent("data:image/png;base64,1234"),
|
||||||
new DataContent("data:audio/wav;base64,1234")
|
new DataContent("data:audio/wav;base64,1234")
|
||||||
};
|
};
|
||||||
});
|
}, new() { SerializerOptions = JsonContext2.Default.Options });
|
||||||
|
|
||||||
var result = await tool.InvokeAsync(
|
var result = await tool.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||||
|
|
@ -288,7 +288,7 @@ public partial class McpServerToolTests
|
||||||
{
|
{
|
||||||
Assert.Same(mockServer.Object, server);
|
Assert.Same(mockServer.Object, server);
|
||||||
return new List<string>() { "42", "43" };
|
return new List<string>() { "42", "43" };
|
||||||
});
|
}, new() { SerializerOptions = JsonContext2.Default.Options });
|
||||||
var result = await tool.InvokeAsync(
|
var result = await tool.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||||
TestContext.Current.CancellationToken);
|
TestContext.Current.CancellationToken);
|
||||||
|
|
@ -632,5 +632,7 @@ public partial class McpServerToolTests
|
||||||
[JsonSerializable(typeof(AsyncDisposableToolType))]
|
[JsonSerializable(typeof(AsyncDisposableToolType))]
|
||||||
[JsonSerializable(typeof(AsyncDisposableAndDisposableToolType))]
|
[JsonSerializable(typeof(AsyncDisposableAndDisposableToolType))]
|
||||||
[JsonSerializable(typeof(JsonSchema))]
|
[JsonSerializable(typeof(JsonSchema))]
|
||||||
|
[JsonSerializable(typeof(List<AIContent>))]
|
||||||
|
[JsonSerializable(typeof(List<string>))]
|
||||||
partial class JsonContext2 : JsonSerializerContext;
|
partial class JsonContext2 : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue