Skip to content

Commit 61092e0

Browse files
authored
Update to latest M.E.AI version (#401)
1 parent 26c3622 commit 61092e0

8 files changed

+106
-10
lines changed

Directory.Packages.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<System10Version>10.0.0-preview.3.25171.5</System10Version>
5-
<MicrosoftExtensionsAIVersion>9.4.3-preview.1.25230.7</MicrosoftExtensionsAIVersion>
5+
<MicrosoftExtensionsAIVersion>9.4.4-preview.1.25259.16</MicrosoftExtensionsAIVersion>
66
</PropertyGroup>
77

88
<!-- Product dependencies netstandard -->
@@ -31,7 +31,7 @@
3131

3232
<!-- Product dependencies shared -->
3333
<ItemGroup>
34-
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(MicrosoftExtensionsAIVersion)" />
34+
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.4.4-preview.1.25259.16" />
3535
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
3636
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
3737
</ItemGroup>

src/ModelContextProtocol/ModelContextProtocol.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
<!-- Dependencies needed by all -->
3535
<ItemGroup>
3636
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
37-
<PackageReference Include="Microsoft.Extensions.AI" />
3837
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
3938
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
4039
<PackageReference Include="System.Net.ServerSentEvents" />

src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.Extensions.AI;
2+
using Microsoft.Extensions.DependencyInjection;
23
using ModelContextProtocol.Protocol.Types;
34
using ModelContextProtocol.Utils;
45
using ModelContextProtocol.Utils.Json;
@@ -68,7 +69,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
6869
Description = options?.Description,
6970
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
7071
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
71-
Services = options?.Services,
72+
CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(),
7273
ConfigureParameterBinding = pi =>
7374
{
7475
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
@@ -110,6 +111,32 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
110111
};
111112
}
112113

114+
if (options?.Services is { } services &&
115+
services.GetService<IServiceProviderIsService>() is { } ispis &&
116+
ispis.IsService(pi.ParameterType))
117+
{
118+
return new()
119+
{
120+
ExcludeFromSchema = true,
121+
BindParameter = (pi, args) =>
122+
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
123+
(pi.HasDefaultValue ? null :
124+
throw new ArgumentException("No service of the requested type was found.")),
125+
};
126+
}
127+
128+
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
129+
{
130+
return new()
131+
{
132+
ExcludeFromSchema = true,
133+
BindParameter = (pi, args) =>
134+
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
135+
(pi.HasDefaultValue ? null :
136+
throw new ArgumentException("No service of the requested type was found.")),
137+
};
138+
}
139+
113140
return default;
114141

115142
static RequestContext<GetPromptRequestParams>? GetRequestContext(AIFunctionArguments args)

src/ModelContextProtocol/Server/AIFunctionMcpServerResource.cs

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using Microsoft.Extensions.AI;
2+
using Microsoft.Extensions.DependencyInjection;
23
using ModelContextProtocol.Protocol.Types;
34
using ModelContextProtocol.Utils;
45
using ModelContextProtocol.Utils.Json;
56
using System.Collections.Concurrent;
67
using System.ComponentModel;
7-
using System.Diagnostics;
88
using System.Diagnostics.CodeAnalysis;
99
using System.Globalization;
1010
using System.Reflection;
@@ -76,7 +76,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
7676
Description = options?.Description,
7777
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
7878
SerializerOptions = McpJsonUtilities.DefaultOptions,
79-
Services = options?.Services,
79+
CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(),
8080
ConfigureParameterBinding = pi =>
8181
{
8282
if (pi.ParameterType == typeof(RequestContext<ReadResourceRequestParams>))
@@ -118,6 +118,32 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
118118
};
119119
}
120120

121+
if (options?.Services is { } services &&
122+
services.GetService<IServiceProviderIsService>() is { } ispis &&
123+
ispis.IsService(pi.ParameterType))
124+
{
125+
return new()
126+
{
127+
ExcludeFromSchema = true,
128+
BindParameter = (pi, args) =>
129+
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
130+
(pi.HasDefaultValue ? null :
131+
throw new ArgumentException("No service of the requested type was found.")),
132+
};
133+
}
134+
135+
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
136+
{
137+
return new()
138+
{
139+
ExcludeFromSchema = true,
140+
BindParameter = (pi, args) =>
141+
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
142+
(pi.HasDefaultValue ? null :
143+
throw new ArgumentException("No service of the requested type was found.")),
144+
};
145+
}
146+
121147
// These parameters are the ones and only ones to include in the schema. The schema
122148
// won't be consumed by anyone other than this instance, which will use it to determine
123149
// which properties should show up in the URI template.

src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.Extensions.AI;
2+
using Microsoft.Extensions.DependencyInjection;
23
using ModelContextProtocol.Protocol.Types;
34
using ModelContextProtocol.Utils;
45
using ModelContextProtocol.Utils.Json;
@@ -60,6 +61,14 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
6061
options);
6162
}
6263

64+
// TODO: Fix the need for this suppression.
65+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:ReflectionToDynamicallyAccessedMembers",
66+
Justification = "AIFunctionFactory ensures that the Type passed to AIFunctionFactoryOptions.CreateInstance has public constructors preserved")]
67+
internal static Func<Type, AIFunctionArguments, object> GetCreateInstanceFunc() =>
68+
static ([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] type, args) => args.Services is { } services ?
69+
ActivatorUtilities.CreateInstance(services, type) :
70+
Activator.CreateInstance(type)!;
71+
6372
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
6473
MethodInfo method, McpServerToolCreateOptions? options) =>
6574
new()
@@ -68,7 +77,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
6877
Description = options?.Description,
6978
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
7079
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
71-
Services = options?.Services,
80+
CreateInstance = GetCreateInstanceFunc(),
7281
ConfigureParameterBinding = pi =>
7382
{
7483
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
@@ -110,6 +119,32 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
110119
};
111120
}
112121

122+
if (options?.Services is { } services &&
123+
services.GetService<IServiceProviderIsService>() is { } ispis &&
124+
ispis.IsService(pi.ParameterType))
125+
{
126+
return new()
127+
{
128+
ExcludeFromSchema = true,
129+
BindParameter = (pi, args) =>
130+
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
131+
(pi.HasDefaultValue ? null :
132+
throw new ArgumentException("No service of the requested type was found.")),
133+
};
134+
}
135+
136+
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
137+
{
138+
return new()
139+
{
140+
ExcludeFromSchema = true,
141+
BindParameter = (pi, args) =>
142+
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
143+
(pi.HasDefaultValue ? null :
144+
throw new ArgumentException("No service of the requested type was found.")),
145+
};
146+
}
147+
113148
return default;
114149

115150
static RequestContext<CallToolRequestParams>? GetRequestContext(AIFunctionArguments args)

tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public async Task SupportsServiceFromDI()
6060
Assert.Contains("something", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
6161
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
6262

63-
await Assert.ThrowsAsync<ArgumentNullException>(async () => await prompt.GetAsync(
63+
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await prompt.GetAsync(
6464
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
6565
TestContext.Current.CancellationToken));
6666

tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ public async Task SupportsServiceFromDI(ServiceLifetime injectedArgumentLifetime
375375

376376
Mock<IMcpServer> mockServer = new();
377377

378-
await Assert.ThrowsAsync<ArgumentNullException>(async () => await resource.ReadAsync(
378+
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await resource.ReadAsync(
379379
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
380380
TestContext.Current.CancellationToken));
381381

tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,18 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets()
156156
[Fact]
157157
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposableTargets()
158158
{
159+
ServiceCollection sc = new();
160+
sc.AddSingleton<MyService>();
161+
IServiceProvider services = sc.BuildServiceProvider();
162+
159163
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
160164
McpServerTool tool1 = McpServerTool.Create(
161165
typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!,
162166
typeof(AsyncDisposableAndDisposableToolType),
163167
options);
164168

165169
var result = await tool1.InvokeAsync(
166-
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
170+
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object) { Services = services },
167171
TestContext.Current.CancellationToken);
168172
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", result.Content[0].Text);
169173
}
@@ -428,6 +432,11 @@ public object InstanceMethod()
428432

429433
private class AsyncDisposableAndDisposableToolType : IAsyncDisposable, IDisposable
430434
{
435+
public AsyncDisposableAndDisposableToolType(MyService service)
436+
{
437+
Assert.NotNull(service);
438+
}
439+
431440
[JsonPropertyOrder(0)]
432441
public int AsyncDisposals { get; private set; }
433442

0 commit comments

Comments
 (0)