Fix some issues with Native AOT (#130)

* Fix some issues with Native AOT

- An anonymous type was being used in McpClient for the initialize result message. Switched to use the type for that purpose that already existed.
- A bunch of the request params / result types were not in the source context or referenced from types that were or from custom converters. Added all the request/result types to the context.
- Improved the warning message for WithTools.
This commit is contained in:
Stephen Toub 2025-03-28 09:50:56 -04:00 • committed by GitHub
commit 2c1672cc1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 53 additions and 27 deletions

View file

@ -6,7 +6,6 @@ using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Shared;
using ModelContextProtocol.Utils.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Text.Json;
namespace ModelContextProtocol.Client;
@ -126,11 +125,11 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
new JsonRpcRequest
{
Method = "initialize",
Params = new
Params = new InitializeRequestParams()
{
protocolVersion = _options.ProtocolVersion,
capabilities = _options.Capabilities ?? new ClientCapabilities(),
clientInfo = _options.ClientInfo
ProtocolVersion = _options.ProtocolVersion,
Capabilities = _options.Capabilities ?? new ClientCapabilities(),
ClientInfo = _options.ClientInfo
}
},
initializationCts.Token).ConfigureAwait(false);

View file

@ -12,7 +12,9 @@ namespace ModelContextProtocol;
/// </summary>
public static partial class McpServerBuilderExtensions
{
private const string RequiresUnreferencedCodeMessage = "This method requires dynamic lookup of method metadata and might not work in Native AOT.";
private const string WithToolsRequiresUnreferencedCodeMessage =
$"The non-generic {nameof(WithTools)} and {nameof(WithToolsFromAssembly)} methods require dynamic lookup of method metadata" +
$"and may not work in Native AOT. Use the generic {nameof(WithTools)} method instead.";
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <typeparam name="TTool">The tool type.</typeparam>
@ -59,7 +61,7 @@ public static partial class McpServerBuilderExtensions
/// types, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
/// </remarks>
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
{
Throw.IfNull(builder);
@ -95,7 +97,7 @@ public static partial class McpServerBuilderExtensions
/// <param name="builder">The builder instance.</param>
/// <param name="toolAssembly">The assembly to load the types from. Null to get the current assembly</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null)
{
Throw.IfNull(builder);

View file

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol.Messages;
/// <summary>
/// A request message in the JSON-RPC protocol.
/// </summary>
public record JsonRpcRequest : IJsonRpcMessageWithId
{
/// <summary>
/// JSON-RPC protocol version. Always "2.0".
/// </summary>
[JsonPropertyName("jsonrpc")]
public string JsonRpc { get; init; } = "2.0";
/// <summary>
/// Request identifier. Must be a string or number and unique within the session.
/// </summary>
[JsonPropertyName("id")]
public RequestId Id { get; set; }
/// <summary>
/// Name of the method to invoke.
/// </summary>
[JsonPropertyName("method")]
public required string Method { get; init; }
/// <summary>
/// Optional parameters for the method.
/// </summary>
[JsonPropertyName("params")]
public object? Params { get; init; }
}

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Logging;
using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Transport;

View file

@ -105,21 +105,52 @@ public static partial class McpJsonUtilities
UseStringEnumConverter = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NumberHandling = JsonNumberHandling.AllowReadingFromString)]
// JSON
[JsonSerializable(typeof(JsonDocument))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(JsonNode))]
// JSON-RPC
[JsonSerializable(typeof(IJsonRpcMessage))]
[JsonSerializable(typeof(JsonRpcRequest))]
[JsonSerializable(typeof(JsonRpcNotification))]
[JsonSerializable(typeof(JsonRpcResponse))]
[JsonSerializable(typeof(JsonRpcError))]
[JsonSerializable(typeof(ServerCapabilities))]
[JsonSerializable(typeof(ClientCapabilities))]
[JsonSerializable(typeof(Implementation))]
[JsonSerializable(typeof(CreateMessageResult))]
[JsonSerializable(typeof(ListRootsResult))]
[JsonSerializable(typeof(InitializeResult))]
// MCP Request Params / Results
[JsonSerializable(typeof(CallToolRequestParams))]
[JsonSerializable(typeof(CallToolResponse))]
[JsonSerializable(typeof(CompleteRequestParams))]
[JsonSerializable(typeof(CompleteResult))]
[JsonSerializable(typeof(CreateMessageRequestParams))]
[JsonSerializable(typeof(CreateMessageResult))]
[JsonSerializable(typeof(EmptyResult))]
[JsonSerializable(typeof(GetPromptRequestParams))]
[JsonSerializable(typeof(GetPromptResult))]
[JsonSerializable(typeof(InitializeRequestParams))]
[JsonSerializable(typeof(InitializeResult))]
[JsonSerializable(typeof(ListPromptsRequestParams))]
[JsonSerializable(typeof(ListPromptsResult))]
[JsonSerializable(typeof(ListResourcesRequestParams))]
[JsonSerializable(typeof(ListResourcesResult))]
[JsonSerializable(typeof(ListResourceTemplatesRequestParams))]
[JsonSerializable(typeof(ListResourceTemplatesResult))]
[JsonSerializable(typeof(ListRootsRequestParams))]
[JsonSerializable(typeof(ListRootsResult))]
[JsonSerializable(typeof(ListToolsRequestParams))]
[JsonSerializable(typeof(ListToolsResult))]
[JsonSerializable(typeof(LoggingMessageNotificationParams))]
[JsonSerializable(typeof(PingResult))]
[JsonSerializable(typeof(ReadResourceRequestParams))]
[JsonSerializable(typeof(ReadResourceResult))]
[JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
[JsonSerializable(typeof(SetLevelRequestParams))]
[JsonSerializable(typeof(SubscribeRequestParams))]
[JsonSerializable(typeof(UnsubscribeFromResourceRequestParams))]
[JsonSerializable(typeof(UnsubscribeRequestParams))]
[ExcludeFromCodeCoverage]
internal sealed partial class JsonContext : JsonSerializerContext;
private static JsonElement ParseJsonElement(ReadOnlySpan<byte> utf8Json)

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using Moq;

View file

@ -4,6 +4,7 @@ using System.Text.Json;
using ModelContextProtocol.Protocol.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Protocol.Types;
namespace ModelContextProtocol.Tests.Utils;
@ -288,19 +289,14 @@ public sealed class InMemoryTestSseServer : IAsyncDisposable
var jsonRpcResponse = new JsonRpcResponse()
{
Id = jsonRpcRequest.Id,
Result = new
Result = new InitializeResult()
{
protocolVersion = "2024-11-05",
capabilities = new
ProtocolVersion = "2024-11-05",
Capabilities = new(),
ServerInfo = new()
{
experimental = (object?)null,
roots = (object?)null,
sampling = (object?)null
},
serverInfo = new
{
name = "ExampleServer",
version = "1.0.0"
Name = "ExampleServer",
Version = "1.0.0"
}
}
};