Skip to content

Commit 0c502c4

Browse files
Update M.E.AI to latest version (#241)
Co-authored-by: Eirik Tsarpalis <[email protected]>
1 parent dfafebd commit 0c502c4

13 files changed

+76
-1147
lines changed

Directory.Packages.props

+12-12
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<System10Version>10.0.0-preview.2.25163.2</System10Version>
5-
<MicrosoftExtensionsAIVersion>9.3.0-preview.1.25161.3</MicrosoftExtensionsAIVersion>
5+
<MicrosoftExtensionsAIVersion>9.4.0-preview.1.25207.5</MicrosoftExtensionsAIVersion>
66
</PropertyGroup>
77

88
<!-- Product dependencies netstandard -->
99
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
10-
<PackageVersion Include="Microsoft.Bcl.Memory" Version="9.0.0" />
10+
<PackageVersion Include="Microsoft.Bcl.Memory" Version="9.0.4" />
1111
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
12-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
12+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
1313
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
1414
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
1515
<PackageVersion Include="System.Threading.Channels" Version="8.0.0" />
@@ -18,15 +18,15 @@
1818
<!-- Product dependencies LTS -->
1919
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
2020
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
21-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
21+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
2222
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
2323
</ItemGroup>
2424

2525
<!-- Product dependencies .NET 9 -->
2626
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
27-
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
28-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
29-
<PackageVersion Include="System.IO.Pipelines" Version="9.0.0" />
27+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
28+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
29+
<PackageVersion Include="System.IO.Pipelines" Version="9.0.4" />
3030
</ItemGroup>
3131

3232
<!-- Product dependencies shared -->
@@ -49,11 +49,11 @@
4949
</PackageVersion>
5050
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
5151
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIVersion)" />
52-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
53-
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
54-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.3" />
55-
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.3" />
56-
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.3" />
52+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
53+
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
54+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
55+
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
56+
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.4" />
5757
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
5858
<PackageVersion Include="Moq" Version="4.20.72" />
5959
<PackageVersion Include="OpenTelemetry" Version="1.11.2" />

samples/ChatWithTools/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
// Create an IChatClient. (This shows using OpenAIClient, but it could be any other IChatClient implementation.)
2626
// Provide your own OPENAI_API_KEY via an environment variable.
2727
using IChatClient chatClient =
28-
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")).AsChatClient("gpt-4o-mini")
28+
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")).GetChatClient("gpt-4o-mini").AsIChatClient()
2929
.AsBuilder().UseFunctionInvocation().Build();
3030

3131
// Have a conversation, making all tools available to the LLM.

src/ModelContextProtocol/Client/McpClientTool.cs

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22
using ModelContextProtocol.Utils.Json;
33
using Microsoft.Extensions.AI;
44
using System.Text.Json;
5+
using System.Collections.ObjectModel;
56

67
namespace ModelContextProtocol.Client;
78

89
/// <summary>Provides an AI function that calls a tool through <see cref="IMcpClient"/>.</summary>
910
public sealed class McpClientTool : AIFunction
1011
{
12+
/// <summary>Additional properties exposed from tools.</summary>
13+
private static readonly ReadOnlyDictionary<string, object?> s_additionalProperties =
14+
new(new Dictionary<string, object?>()
15+
{
16+
["Strict"] = false, // some MCP schemas may not meet "strict" requirements
17+
});
18+
1119
private readonly IMcpClient _client;
1220
private readonly string _name;
1321
private readonly string _description;
@@ -62,14 +70,13 @@ public McpClientTool WithDescription(string description)
6270
public override JsonSerializerOptions JsonSerializerOptions { get; }
6371

6472
/// <inheritdoc/>
65-
protected async override Task<object?> InvokeCoreAsync(
66-
IEnumerable<KeyValuePair<string, object?>> arguments, CancellationToken cancellationToken)
67-
{
68-
IReadOnlyDictionary<string, object?> argDict =
69-
arguments as IReadOnlyDictionary<string, object?> ??
70-
arguments.ToDictionary();
73+
public override IReadOnlyDictionary<string, object?> AdditionalProperties => s_additionalProperties;
7174

72-
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, argDict, JsonSerializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
75+
/// <inheritdoc/>
76+
protected async override ValueTask<object?> InvokeCoreAsync(
77+
AIFunctionArguments arguments, CancellationToken cancellationToken)
78+
{
79+
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, arguments, JsonSerializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
7380
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
7481
}
7582
}

src/ModelContextProtocol/ModelContextProtocol.csproj

+1-5
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,10 @@
3030
<!-- Dependencies needed by all -->
3131
<ItemGroup>
3232
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
33+
<PackageReference Include="Microsoft.Extensions.AI" />
3334
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
3435
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
3536
<PackageReference Include="System.Net.ServerSentEvents" />
36-
37-
<!-- Temporarily removed until new version can be picked up that
38-
has reduced dependencies:
39-
<PackageReference Include="Microsoft.Extensions.AI" />
40-
-->
4137
</ItemGroup>
4238

4339
<ItemGroup>

src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs

+20-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using ModelContextProtocol.Protocol.Types;
44
using ModelContextProtocol.Utils;
5-
using ModelContextProtocol.Utils.Json;
65
using System.Diagnostics.CodeAnalysis;
76
using System.Reflection;
87
using System.Text.Json;
@@ -12,10 +11,6 @@ namespace ModelContextProtocol.Server;
1211
/// <summary>Provides an <see cref="McpServerPrompt"/> that's implemented via an <see cref="AIFunction"/>.</summary>
1312
internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
1413
{
15-
/// <summary>Key used temporarily for flowing request context into an AIFunction.</summary>
16-
/// <remarks>This will be replaced with use of AIFunctionArguments.Context.</remarks>
17-
internal const string RequestContextKey = "__temporary_RequestContext";
18-
1914
/// <summary>
2015
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
2116
/// </summary>
@@ -40,17 +35,10 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
4035
{
4136
Throw.IfNull(method);
4237

43-
// TODO: Once this repo consumes a new build of Microsoft.Extensions.AI containing
44-
// https://github.com/dotnet/extensions/pull/6158,
45-
// https://github.com/dotnet/extensions/pull/6162, and
46-
// https://github.com/dotnet/extensions/pull/6175, switch over to using the real
47-
// AIFunctionFactory, delete the TemporaryXx types, and fix-up the mechanism by
48-
// which the arguments are passed.
49-
5038
options = DeriveOptions(method, options);
5139

5240
return Create(
53-
TemporaryAIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
41+
AIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
5442
options);
5543
}
5644

@@ -67,17 +55,17 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
6755
options = DeriveOptions(method, options);
6856

6957
return Create(
70-
TemporaryAIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)),
58+
AIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)),
7159
options);
7260
}
7361

74-
private static TemporaryAIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
62+
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
7563
MethodInfo method, McpServerPromptCreateOptions? options) =>
7664
new()
7765
{
7866
Name = options?.Name ?? method.GetCustomAttribute<McpServerPromptAttribute>()?.Name,
7967
Description = options?.Description,
80-
MarshalResult = static (result, _, cancellationToken) => Task.FromResult(result),
68+
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
8169
ConfigureParameterBinding = pi =>
8270
{
8371
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
@@ -129,9 +117,9 @@ private static TemporaryAIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
129117

130118
return default;
131119

132-
static RequestContext<GetPromptRequestParams>? GetRequestContext(IReadOnlyDictionary<string, object?> args)
120+
static RequestContext<GetPromptRequestParams>? GetRequestContext(AIFunctionArguments args)
133121
{
134-
if (args.TryGetValue(RequestContextKey, out var orc) &&
122+
if (args.Context?.TryGetValue(typeof(RequestContext<GetPromptRequestParams>), out var orc) is true &&
135123
orc is RequestContext<GetPromptRequestParams> requestContext)
136124
{
137125
return requestContext;
@@ -204,14 +192,22 @@ public override async Task<GetPromptResult> GetAsync(
204192
RequestContext<GetPromptRequestParams> request, CancellationToken cancellationToken = default)
205193
{
206194
Throw.IfNull(request);
207-
208195
cancellationToken.ThrowIfCancellationRequested();
209196

210-
// TODO: Once we shift to the real AIFunctionFactory, the request should be passed via AIFunctionArguments.Context.
211-
Dictionary<string, object?> arguments = request.Params?.Arguments is { } paramArgs ?
212-
paramArgs.ToDictionary(entry => entry.Key, entry => entry.Value.AsObject()) :
213-
[];
214-
arguments[RequestContextKey] = request;
197+
AIFunctionArguments arguments = new()
198+
{
199+
Services = request.Server?.Services,
200+
Context = new Dictionary<object, object?>() { [typeof(RequestContext<GetPromptRequestParams>)] = request }
201+
};
202+
203+
var argDict = request.Params?.Arguments;
204+
if (argDict is not null)
205+
{
206+
foreach (var kvp in argDict)
207+
{
208+
arguments[kvp.Key] = kvp.Value;
209+
}
210+
}
215211

216212
object? result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);
217213

src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs

+20-25
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ namespace ModelContextProtocol.Server;
1212
/// <summary>Provides an <see cref="McpServerTool"/> that's implemented via an <see cref="AIFunction"/>.</summary>
1313
internal sealed class AIFunctionMcpServerTool : McpServerTool
1414
{
15-
/// <summary>Key used temporarily for flowing request context into an AIFunction.</summary>
16-
/// <remarks>This will be replaced with use of AIFunctionArguments.Context.</remarks>
17-
internal const string RequestContextKey = "__temporary_RequestContext";
18-
1915
/// <summary>
2016
/// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance.
2117
/// </summary>
@@ -40,17 +36,10 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
4036
{
4137
Throw.IfNull(method);
4238

43-
// TODO: Once this repo consumes a new build of Microsoft.Extensions.AI containing
44-
// https://github.com/dotnet/extensions/pull/6158,
45-
// https://github.com/dotnet/extensions/pull/6162, and
46-
// https://github.com/dotnet/extensions/pull/6175, switch over to using the real
47-
// AIFunctionFactory, delete the TemporaryXx types, and fix-up the mechanism by
48-
// which the arguments are passed.
49-
5039
options = DeriveOptions(method, options);
5140

5241
return Create(
53-
TemporaryAIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
42+
AIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
5443
options);
5544
}
5645

@@ -67,17 +56,17 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
6756
options = DeriveOptions(method, options);
6857

6958
return Create(
70-
TemporaryAIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)),
59+
AIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)),
7160
options);
7261
}
7362

74-
private static TemporaryAIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
63+
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
7564
MethodInfo method, McpServerToolCreateOptions? options) =>
7665
new()
7766
{
7867
Name = options?.Name ?? method.GetCustomAttribute<McpServerToolAttribute>()?.Name,
7968
Description = options?.Description,
80-
MarshalResult = static (result, _, cancellationToken) => Task.FromResult(result),
69+
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
8170
ConfigureParameterBinding = pi =>
8271
{
8372
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
@@ -150,9 +139,9 @@ private static TemporaryAIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
150139

151140
return default;
152141

153-
static RequestContext<CallToolRequestParams>? GetRequestContext(IReadOnlyDictionary<string, object?> args)
142+
static RequestContext<CallToolRequestParams>? GetRequestContext(AIFunctionArguments args)
154143
{
155-
if (args.TryGetValue(RequestContextKey, out var orc) &&
144+
if (args.Context?.TryGetValue(typeof(RequestContext<CallToolRequestParams>), out var orc) is true &&
156145
orc is RequestContext<CallToolRequestParams> requestContext)
157146
{
158147
return requestContext;
@@ -251,14 +240,22 @@ public override async Task<CallToolResponse> InvokeAsync(
251240
RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken = default)
252241
{
253242
Throw.IfNull(request);
254-
255243
cancellationToken.ThrowIfCancellationRequested();
256244

257-
// TODO: Once we shift to the real AIFunctionFactory, the request should be passed via AIFunctionArguments.Context.
258-
Dictionary<string, object?> arguments = request.Params?.Arguments is { } paramArgs ?
259-
paramArgs.ToDictionary(entry => entry.Key, entry => entry.Value.AsObject()) :
260-
[];
261-
arguments[RequestContextKey] = request;
245+
AIFunctionArguments arguments = new()
246+
{
247+
Services = request.Server?.Services,
248+
Context = new Dictionary<object, object?>() { [typeof(RequestContext<CallToolRequestParams>)] = request }
249+
};
250+
251+
var argDict = request.Params?.Arguments;
252+
if (argDict is not null)
253+
{
254+
foreach (var kvp in argDict)
255+
{
256+
arguments[kvp.Key] = kvp.Value;
257+
}
258+
}
262259

263260
object? result;
264261
try
@@ -313,8 +310,6 @@ public override async Task<CallToolResponse> InvokeAsync(
313310

314311
CallToolResponse callToolResponse => callToolResponse,
315312

316-
// TODO https://github.com/modelcontextprotocol/csharp-sdk/issues/69:
317-
// Add specialization for annotations.
318313
_ => new()
319314
{
320315
Content = [new()

0 commit comments

Comments
 (0)