Update to latest M.E.AI version (#401)
This commit is contained in:
parent
26c3622101
commit
61092e0266
8 changed files with 106 additions and 10 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
<System10Version>10.0.0-preview.3.25171.5</System10Version>
|
<System10Version>10.0.0-preview.3.25171.5</System10Version>
|
||||||
<MicrosoftExtensionsAIVersion>9.4.3-preview.1.25230.7</MicrosoftExtensionsAIVersion>
|
<MicrosoftExtensionsAIVersion>9.4.4-preview.1.25259.16</MicrosoftExtensionsAIVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Product dependencies netstandard -->
|
<!-- Product dependencies netstandard -->
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
<!-- Product dependencies shared -->
|
<!-- Product dependencies shared -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(MicrosoftExtensionsAIVersion)" />
|
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.4.4-preview.1.25259.16" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
|
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
|
||||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
|
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@
|
||||||
<!-- Dependencies needed by all -->
|
<!-- Dependencies needed by all -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
|
|
@ -68,7 +69,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,
|
||||||
Services = options?.Services,
|
CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(),
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
|
||||||
|
|
@ -110,6 +111,32 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.Services is { } services &&
|
||||||
|
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||||
|
ispis.IsService(pi.ParameterType))
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ExcludeFromSchema = true,
|
||||||
|
BindParameter = (pi, args) =>
|
||||||
|
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||||
|
(pi.HasDefaultValue ? null :
|
||||||
|
throw new ArgumentException("No service of the requested type was found.")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ExcludeFromSchema = true,
|
||||||
|
BindParameter = (pi, args) =>
|
||||||
|
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||||
|
(pi.HasDefaultValue ? null :
|
||||||
|
throw new ArgumentException("No service of the requested type was found.")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
static RequestContext<GetPromptRequestParams>? GetRequestContext(AIFunctionArguments args)
|
static RequestContext<GetPromptRequestParams>? GetRequestContext(AIFunctionArguments args)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -76,7 +76,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
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 = McpJsonUtilities.DefaultOptions,
|
||||||
Services = options?.Services,
|
CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(),
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<ReadResourceRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<ReadResourceRequestParams>))
|
||||||
|
|
@ -118,6 +118,32 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.Services is { } services &&
|
||||||
|
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||||
|
ispis.IsService(pi.ParameterType))
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ExcludeFromSchema = true,
|
||||||
|
BindParameter = (pi, args) =>
|
||||||
|
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||||
|
(pi.HasDefaultValue ? null :
|
||||||
|
throw new ArgumentException("No service of the requested type was found.")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ExcludeFromSchema = true,
|
||||||
|
BindParameter = (pi, args) =>
|
||||||
|
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||||
|
(pi.HasDefaultValue ? null :
|
||||||
|
throw new ArgumentException("No service of the requested type was found.")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// These parameters are the ones and only ones to include in the schema. The schema
|
// These parameters are the ones and only ones to include in the schema. The schema
|
||||||
// won't be consumed by anyone other than this instance, which will use it to determine
|
// won't be consumed by anyone other than this instance, which will use it to determine
|
||||||
// which properties should show up in the URI template.
|
// which properties should show up in the URI template.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
|
|
@ -60,6 +61,14 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
options);
|
options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Fix the need for this suppression.
|
||||||
|
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:ReflectionToDynamicallyAccessedMembers",
|
||||||
|
Justification = "AIFunctionFactory ensures that the Type passed to AIFunctionFactoryOptions.CreateInstance has public constructors preserved")]
|
||||||
|
internal static Func<Type, AIFunctionArguments, object> GetCreateInstanceFunc() =>
|
||||||
|
static ([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] type, args) => args.Services is { } services ?
|
||||||
|
ActivatorUtilities.CreateInstance(services, type) :
|
||||||
|
Activator.CreateInstance(type)!;
|
||||||
|
|
||||||
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
|
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
|
||||||
MethodInfo method, McpServerToolCreateOptions? options) =>
|
MethodInfo method, McpServerToolCreateOptions? options) =>
|
||||||
new()
|
new()
|
||||||
|
|
@ -68,7 +77,7 @@ internal sealed 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,
|
||||||
Services = options?.Services,
|
CreateInstance = GetCreateInstanceFunc(),
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
||||||
|
|
@ -110,6 +119,32 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.Services is { } services &&
|
||||||
|
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||||
|
ispis.IsService(pi.ParameterType))
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ExcludeFromSchema = true,
|
||||||
|
BindParameter = (pi, args) =>
|
||||||
|
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||||
|
(pi.HasDefaultValue ? null :
|
||||||
|
throw new ArgumentException("No service of the requested type was found.")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ExcludeFromSchema = true,
|
||||||
|
BindParameter = (pi, args) =>
|
||||||
|
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||||
|
(pi.HasDefaultValue ? null :
|
||||||
|
throw new ArgumentException("No service of the requested type was found.")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
static RequestContext<CallToolRequestParams>? GetRequestContext(AIFunctionArguments args)
|
static RequestContext<CallToolRequestParams>? GetRequestContext(AIFunctionArguments args)
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ public class McpServerPromptTests
|
||||||
Assert.Contains("something", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
Assert.Contains("something", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||||
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await prompt.GetAsync(
|
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await prompt.GetAsync(
|
||||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||||
TestContext.Current.CancellationToken));
|
TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ public partial class McpServerResourceTests
|
||||||
|
|
||||||
Mock<IMcpServer> mockServer = new();
|
Mock<IMcpServer> mockServer = new();
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await resource.ReadAsync(
|
await Assert.ThrowsAnyAsync<ArgumentException>(async () => 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));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,10 @@ public partial class McpServerToolTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposableTargets()
|
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposableTargets()
|
||||||
{
|
{
|
||||||
|
ServiceCollection sc = new();
|
||||||
|
sc.AddSingleton<MyService>();
|
||||||
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
|
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
|
||||||
McpServerTool tool1 = McpServerTool.Create(
|
McpServerTool tool1 = McpServerTool.Create(
|
||||||
typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!,
|
typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!,
|
||||||
|
|
@ -163,7 +167,7 @@ public partial class McpServerToolTests
|
||||||
options);
|
options);
|
||||||
|
|
||||||
var result = await tool1.InvokeAsync(
|
var result = await tool1.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
|
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object) { Services = services },
|
||||||
TestContext.Current.CancellationToken);
|
TestContext.Current.CancellationToken);
|
||||||
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", result.Content[0].Text);
|
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", result.Content[0].Text);
|
||||||
}
|
}
|
||||||
|
|
@ -428,6 +432,11 @@ public partial class McpServerToolTests
|
||||||
|
|
||||||
private class AsyncDisposableAndDisposableToolType : IAsyncDisposable, IDisposable
|
private class AsyncDisposableAndDisposableToolType : IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
|
public AsyncDisposableAndDisposableToolType(MyService service)
|
||||||
|
{
|
||||||
|
Assert.NotNull(service);
|
||||||
|
}
|
||||||
|
|
||||||
[JsonPropertyOrder(0)]
|
[JsonPropertyOrder(0)]
|
||||||
public int AsyncDisposals { get; private set; }
|
public int AsyncDisposals { get; private set; }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue