Turn on the AOT analyzer. (#92)
* Turn on the AOT analyzer. * Address feedback * Add missing types. * Fix build and react to conflicts. * Fix failing test. * Address feedback.
This commit is contained in:
parent
c0e1d0dd9a
commit
e7fa6194f6
22 changed files with 522 additions and 68 deletions
|
|
@ -0,0 +1,164 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
namespace System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the types of members that are dynamically accessed.
|
||||||
|
///
|
||||||
|
/// This enumeration has a <see cref="FlagsAttribute"/> attribute that allows a
|
||||||
|
/// bitwise combination of its member values.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
internal enum DynamicallyAccessedMemberTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies no members.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the default, parameterless public constructor.
|
||||||
|
/// </summary>
|
||||||
|
PublicParameterlessConstructor = 0x0001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public constructors.
|
||||||
|
/// </summary>
|
||||||
|
PublicConstructors = 0x0002 | PublicParameterlessConstructor,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public constructors.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicConstructors = 0x0004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public methods.
|
||||||
|
/// </summary>
|
||||||
|
PublicMethods = 0x0008,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public methods.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicMethods = 0x0010,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public fields.
|
||||||
|
/// </summary>
|
||||||
|
PublicFields = 0x0020,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public fields.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicFields = 0x0040,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public nested types.
|
||||||
|
/// </summary>
|
||||||
|
PublicNestedTypes = 0x0080,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public nested types.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicNestedTypes = 0x0100,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public properties.
|
||||||
|
/// </summary>
|
||||||
|
PublicProperties = 0x0200,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public properties.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicProperties = 0x0400,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public events.
|
||||||
|
/// </summary>
|
||||||
|
PublicEvents = 0x0800,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public events.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicEvents = 0x1000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all interfaces implemented by the type.
|
||||||
|
/// </summary>
|
||||||
|
Interfaces = 0x2000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public constructors, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public methods, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicMethodsWithInherited = NonPublicMethods | 0x8000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public fields, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicFieldsWithInherited = NonPublicFields | 0x10000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public nested types, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicNestedTypesWithInherited = NonPublicNestedTypes | 0x20000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public properties, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicPropertiesWithInherited = NonPublicProperties | 0x40000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all non-public events, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
NonPublicEventsWithInherited = NonPublicEvents | 0x80000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public constructors, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
PublicConstructorsWithInherited = PublicConstructors | 0x100000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all public nested types, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
PublicNestedTypesWithInherited = PublicNestedTypes | 0x200000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all constructors, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
AllConstructors = PublicConstructorsWithInherited | NonPublicConstructorsWithInherited,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all methods, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
AllMethods = PublicMethods | NonPublicMethodsWithInherited,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all fields, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
AllFields = PublicFields | NonPublicFieldsWithInherited,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all nested types, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
AllNestedTypes = PublicNestedTypesWithInherited | NonPublicNestedTypesWithInherited,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all properties, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
AllProperties = PublicProperties | NonPublicPropertiesWithInherited,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all events, including those inherited from base classes.
|
||||||
|
/// </summary>
|
||||||
|
AllEvents = PublicEvents | NonPublicEventsWithInherited,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies all members.
|
||||||
|
/// </summary>
|
||||||
|
All = ~None
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
namespace System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that certain members on a specified <see cref="Type"/> are accessed dynamically,
|
||||||
|
/// for example through <see cref="Reflection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This allows tools to understand which members are being accessed during the execution
|
||||||
|
/// of a program.
|
||||||
|
///
|
||||||
|
/// This attribute is valid on members whose type is <see cref="Type"/> or <see cref="string"/>.
|
||||||
|
///
|
||||||
|
/// When this attribute is applied to a location of type <see cref="string"/>, the assumption is
|
||||||
|
/// that the string represents a fully qualified type name.
|
||||||
|
///
|
||||||
|
/// When this attribute is applied to a class, interface, or struct, the members specified
|
||||||
|
/// can be accessed dynamically on <see cref="Type"/> instances returned from calling
|
||||||
|
/// <see cref="object.GetType"/> on instances of that class, interface, or struct.
|
||||||
|
///
|
||||||
|
/// If the attribute is applied to a method it's treated as a special case and it implies
|
||||||
|
/// the attribute should be applied to the "this" parameter of the method. As such the attribute
|
||||||
|
/// should only be used on instance methods of types assignable to System.Type (or string, but no methods
|
||||||
|
/// will use it there).
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(
|
||||||
|
AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter |
|
||||||
|
AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method |
|
||||||
|
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct,
|
||||||
|
Inherited = false)]
|
||||||
|
internal sealed class DynamicallyAccessedMembersAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DynamicallyAccessedMembersAttribute"/> class
|
||||||
|
/// with the specified member types.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberTypes">The types of members dynamically accessed.</param>
|
||||||
|
public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes)
|
||||||
|
{
|
||||||
|
MemberTypes = memberTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="DynamicallyAccessedMemberTypes"/> which specifies the type
|
||||||
|
/// of members dynamically accessed.
|
||||||
|
/// </summary>
|
||||||
|
public DynamicallyAccessedMemberTypes MemberTypes { get; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
namespace System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the specified method requires the ability to generate new code at runtime,
|
||||||
|
/// for example through <see cref="Reflection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This allows tools to understand which methods are unsafe to call when compiling ahead of time.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
|
||||||
|
internal sealed class RequiresDynamicCodeAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RequiresDynamicCodeAttribute"/> class
|
||||||
|
/// with the specified message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">
|
||||||
|
/// A message that contains information about the usage of dynamic code.
|
||||||
|
/// </param>
|
||||||
|
public RequiresDynamicCodeAttribute(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a message that contains information about the usage of dynamic code.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an optional URL that contains more information about the method,
|
||||||
|
/// why it requires dynamic code, and what options a consumer has to deal with it.
|
||||||
|
/// </summary>
|
||||||
|
public string? Url { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
namespace System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the specified method requires dynamic access to code that is not referenced
|
||||||
|
/// statically, for example through <see cref="System.Reflection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This allows tools to understand which methods are unsafe to call when removing unreferenced
|
||||||
|
/// code from an application.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
|
||||||
|
internal sealed class RequiresUnreferencedCodeAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RequiresUnreferencedCodeAttribute"/> class
|
||||||
|
/// with the specified message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">
|
||||||
|
/// A message that contains information about the usage of unreferenced code.
|
||||||
|
/// </param>
|
||||||
|
public RequiresUnreferencedCodeAttribute(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a message that contains information about the usage of unreferenced code.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an optional URL that contains more information about the method,
|
||||||
|
/// why it requires unreferenced code, and what options a consumer has to deal with it.
|
||||||
|
/// </summary>
|
||||||
|
public string? Url { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
namespace System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a
|
||||||
|
/// single code artifact.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="UnconditionalSuppressMessageAttribute"/> is different than
|
||||||
|
/// <see cref="SuppressMessageAttribute"/> in that it doesn't have a
|
||||||
|
/// <see cref="ConditionalAttribute"/>. So it is always preserved in the compiled assembly.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
|
||||||
|
internal sealed class UnconditionalSuppressMessageAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UnconditionalSuppressMessageAttribute"/>
|
||||||
|
/// class, specifying the category of the tool and the identifier for an analysis rule.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category">The category for the attribute.</param>
|
||||||
|
/// <param name="checkId">The identifier of the analysis rule the attribute applies to.</param>
|
||||||
|
public UnconditionalSuppressMessageAttribute(string category, string checkId)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
CheckId = checkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category identifying the classification of the attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The <see cref="Category"/> property describes the tool or tool analysis category
|
||||||
|
/// for which a message suppression attribute applies.
|
||||||
|
/// </remarks>
|
||||||
|
public string Category { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the identifier of the analysis tool rule to be suppressed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Concatenated together, the <see cref="Category"/> and <see cref="CheckId"/>
|
||||||
|
/// properties form a unique check identifier.
|
||||||
|
/// </remarks>
|
||||||
|
public string CheckId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the scope of the code that is relevant for the attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The Scope property is an optional argument that specifies the metadata scope for which
|
||||||
|
/// the attribute is relevant.
|
||||||
|
/// </remarks>
|
||||||
|
public string? Scope { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a fully qualified path that represents the target of the attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The <see cref="Target"/> property is an optional argument identifying the analysis target
|
||||||
|
/// of the attribute. An example value is "System.IO.Stream.ctor():System.Void".
|
||||||
|
/// Because it is fully qualified, it can be long, particularly for targets such as parameters.
|
||||||
|
/// The analysis tool user interface should be capable of automatically formatting the parameter.
|
||||||
|
/// </remarks>
|
||||||
|
public string? Target { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an optional argument expanding on exclusion criteria.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The <see cref="MessageId"/> property is an optional argument that specifies additional
|
||||||
|
/// exclusion where the literal metadata target is not sufficiently precise. For example,
|
||||||
|
/// the <see cref="UnconditionalSuppressMessageAttribute"/> cannot be applied within a method,
|
||||||
|
/// and it may be desirable to suppress a violation against a statement in the method that will
|
||||||
|
/// give a rule violation, but not against all statements in the method.
|
||||||
|
/// </remarks>
|
||||||
|
public string? MessageId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the justification for suppressing the code analysis message.
|
||||||
|
/// </summary>
|
||||||
|
public string? Justification { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
<AnalysisMode>All</AnalysisMode>
|
<AnalysisMode>All</AnalysisMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
|
||||||
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- license and package properties -->
|
<!-- license and package properties -->
|
||||||
<PackageId>McpDotNet.Extensions.AI</PackageId>
|
<PackageId>McpDotNet.Extensions.AI</PackageId>
|
||||||
|
|
@ -37,4 +41,8 @@
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.3" />
|
<PackageReference Include="System.Text.Json" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||||
|
<Compile Include="..\Common\Polyfills\**\*.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@ using McpDotNet.Protocol.Messages;
|
||||||
using McpDotNet.Protocol.Transport;
|
using McpDotNet.Protocol.Transport;
|
||||||
using McpDotNet.Protocol.Types;
|
using McpDotNet.Protocol.Types;
|
||||||
using McpDotNet.Shared;
|
using McpDotNet.Shared;
|
||||||
|
using McpDotNet.Utils.Json;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace McpDotNet.Client;
|
namespace McpDotNet.Client;
|
||||||
|
|
@ -139,7 +138,10 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
|
||||||
initializationCts.Token).ConfigureAwait(false);
|
initializationCts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
// Store server information
|
// Store server information
|
||||||
_logger.ServerCapabilitiesReceived(EndpointName, JsonSerializer.Serialize(initializeResponse.Capabilities), JsonSerializer.Serialize(initializeResponse.ServerInfo));
|
_logger.ServerCapabilitiesReceived(EndpointName,
|
||||||
|
capabilities: JsonSerializer.Serialize(initializeResponse.Capabilities, JsonSerializerOptionsExtensions.JsonContext.Default.ServerCapabilities),
|
||||||
|
serverInfo: JsonSerializer.Serialize(initializeResponse.ServerInfo, JsonSerializerOptionsExtensions.JsonContext.Default.Implementation));
|
||||||
|
|
||||||
ServerCapabilities = initializeResponse.Capabilities;
|
ServerCapabilities = initializeResponse.Capabilities;
|
||||||
ServerInfo = initializeResponse.ServerInfo;
|
ServerInfo = initializeResponse.ServerInfo;
|
||||||
ServerInstructions = initializeResponse.Instructions;
|
ServerInstructions = initializeResponse.Instructions;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using McpDotNet.Protocol.Messages;
|
using McpDotNet.Protocol.Messages;
|
||||||
using McpDotNet.Protocol.Types;
|
using McpDotNet.Protocol.Types;
|
||||||
using McpDotNet.Utils;
|
using McpDotNet.Utils;
|
||||||
|
using McpDotNet.Utils.Json;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -465,6 +466,8 @@ public static class McpClientExtensions
|
||||||
/// <summary>Provides an AI function that calls a tool through <see cref="IMcpClient"/>.</summary>
|
/// <summary>Provides an AI function that calls a tool through <see cref="IMcpClient"/>.</summary>
|
||||||
private sealed class McpAIFunction(IMcpClient client, Tool tool) : AIFunction
|
private sealed class McpAIFunction(IMcpClient client, Tool tool) : AIFunction
|
||||||
{
|
{
|
||||||
|
private JsonElement? _jsonSchema;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string Name => tool.Name;
|
public override string Name => tool.Name;
|
||||||
|
|
||||||
|
|
@ -472,7 +475,7 @@ public static class McpClientExtensions
|
||||||
public override string Description => tool.Description ?? string.Empty;
|
public override string Description => tool.Description ?? string.Empty;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override JsonElement JsonSchema =>
|
public override JsonElement JsonSchema => _jsonSchema ??=
|
||||||
JsonSerializer.SerializeToElement(new Dictionary<string, object>
|
JsonSerializer.SerializeToElement(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
["type"] = "object",
|
["type"] = "object",
|
||||||
|
|
@ -480,7 +483,7 @@ public static class McpClientExtensions
|
||||||
["description"] = tool.Description ?? string.Empty,
|
["description"] = tool.Description ?? string.Empty,
|
||||||
["properties"] = tool.InputSchema?.Properties ?? [],
|
["properties"] = tool.InputSchema?.Properties ?? [],
|
||||||
["required"] = tool.InputSchema?.Required ?? []
|
["required"] = tool.InputSchema?.Required ?? []
|
||||||
});
|
}, JsonSerializerOptionsExtensions.JsonContext.Default.DictionaryStringObject);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected async override Task<object?> InvokeCoreAsync(
|
protected async override Task<object?> InvokeCoreAsync(
|
||||||
|
|
@ -498,8 +501,7 @@ public static class McpClientExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
CallToolResponse result = await client.CallToolAsync(tool.Name, argDict, cancellationToken).ConfigureAwait(false);
|
CallToolResponse result = await client.CallToolAsync(tool.Name, argDict, cancellationToken).ConfigureAwait(false);
|
||||||
|
return JsonSerializer.SerializeToElement(result, JsonSerializerOptionsExtensions.JsonContext.Default.CallToolResponse);
|
||||||
return JsonSerializer.SerializeToElement(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
using McpDotNet.Protocol.Types;
|
using McpDotNet.Protocol.Types;
|
||||||
using McpDotNet.Server;
|
using McpDotNet.Server;
|
||||||
using McpDotNet.Utils;
|
using McpDotNet.Utils;
|
||||||
|
using McpDotNet.Utils.Json;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
|
@ -15,21 +15,28 @@ namespace McpDotNet;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static partial class McpServerBuilderExtensions
|
public static partial class McpServerBuilderExtensions
|
||||||
{
|
{
|
||||||
|
private const string RequiresUnreferencedCodeMessage = "This method requires dynamic lookup of method metadata and might not work in Native AOT.";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a tool to the server.
|
/// Adds a tool to the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TTool">The tool type.</typeparam>
|
/// <typeparam name="TTool">The tool type.</typeparam>
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
public static IMcpServerBuilder WithTool<TTool>(this IMcpServerBuilder builder)
|
public static IMcpServerBuilder WithTool<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TTool>(this IMcpServerBuilder builder)
|
||||||
{
|
{
|
||||||
return WithTools(builder, typeof(TTool));
|
Throw.IfNull(builder);
|
||||||
|
List<AIFunction> functions = [];
|
||||||
|
|
||||||
|
PopulateFunctions(typeof(TTool), functions);
|
||||||
|
return WithTools(builder, functions);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds all tools marked with <see cref="McpToolTypeAttribute"/> from the current assembly to the server.
|
/// Adds all tools marked with <see cref="McpToolTypeAttribute"/> from the current assembly to the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
|
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
|
||||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder)
|
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder)
|
||||||
{
|
{
|
||||||
return WithToolsFromAssembly(builder, Assembly.GetCallingAssembly());
|
return WithToolsFromAssembly(builder, Assembly.GetCallingAssembly());
|
||||||
|
|
@ -42,6 +49,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
|
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
|
||||||
|
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
|
||||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
|
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
|
||||||
{
|
{
|
||||||
Throw.IfNull(builder);
|
Throw.IfNull(builder);
|
||||||
|
|
@ -56,18 +64,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
throw new ArgumentNullException(nameof(toolTypes), $"A tool type provided by the enumerator was null.");
|
throw new ArgumentNullException(nameof(toolTypes), $"A tool type provided by the enumerator was null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var method in toolType.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
PopulateFunctions(toolType, functions);
|
||||||
{
|
|
||||||
if (method.GetCustomAttribute<McpToolAttribute>() is not { } attribute)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
functions.Add(AIFunctionFactory.Create(method, target: null, new()
|
|
||||||
{
|
|
||||||
Name = attribute.Name ?? method.Name,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return WithTools(builder, functions);
|
return WithTools(builder, functions);
|
||||||
|
|
@ -99,7 +96,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
{
|
{
|
||||||
Name = function.Name,
|
Name = function.Name,
|
||||||
Description = function.Description,
|
Description = function.Description,
|
||||||
InputSchema = JsonSerializer.Deserialize<JsonSchema>(function.JsonSchema),
|
InputSchema = JsonSerializer.Deserialize(function.JsonSchema, JsonSerializerOptionsExtensions.JsonContext.Default.JsonSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
callbacks.Add(function.Name, async (request, cancellationToken) =>
|
callbacks.Add(function.Name, async (request, cancellationToken) =>
|
||||||
|
|
@ -155,6 +152,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="assembly">The assembly to load the types from. Null to get the current assembly</param>
|
/// <param name="assembly">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>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
|
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
|
||||||
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? assembly = null)
|
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? assembly = null)
|
||||||
{
|
{
|
||||||
assembly ??= Assembly.GetCallingAssembly();
|
assembly ??= Assembly.GetCallingAssembly();
|
||||||
|
|
@ -182,4 +180,22 @@ public static partial class McpServerBuilderExtensions
|
||||||
WithTools(builder, toolTypes) :
|
WithTools(builder, toolTypes) :
|
||||||
builder;
|
builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void PopulateFunctions(
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type toolType,
|
||||||
|
List<AIFunction> functions)
|
||||||
|
{
|
||||||
|
foreach (var method in toolType.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
||||||
|
{
|
||||||
|
if (method.GetCustomAttribute<McpToolAttribute>() is not { } attribute)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
functions.Add(AIFunctionFactory.Create(method, target: null, new()
|
||||||
|
{
|
||||||
|
Name = attribute.Name ?? method.Name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
namespace McpDotNet.Protocol.Messages;
|
using McpDotNet.Utils.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace McpDotNet.Protocol.Messages;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base interface for all JSON-RPC messages in the MCP protocol.
|
/// Base interface for all JSON-RPC messages in the MCP protocol.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(JsonRpcMessageConverter))]
|
||||||
public interface IJsonRpcMessage
|
public interface IJsonRpcMessage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ public sealed class HttpListenerSseServerTransport : TransportBase, IServerTrans
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(message, _jsonOptions);
|
var json = JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
_logger.TransportSendingMessage(EndpointName, id, json);
|
_logger.TransportSendingMessage(EndpointName, id, json);
|
||||||
|
|
||||||
await _httpServerProvider.SendEvent(json, "message").ConfigureAwait(false);
|
await _httpServerProvider.SendEvent(json, "message").ConfigureAwait(false);
|
||||||
|
|
@ -125,7 +125,7 @@ public sealed class HttpListenerSseServerTransport : TransportBase, IServerTrans
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = JsonSerializer.Deserialize<IJsonRpcMessage>(request, _jsonOptions);
|
var message = JsonSerializer.Deserialize(request, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
if (message != null)
|
if (message != null)
|
||||||
{
|
{
|
||||||
// Fire-and-forget the message to the message channel
|
// Fire-and-forget the message to the message channel
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
|
||||||
throw new InvalidOperationException("Transport not connected");
|
throw new InvalidOperationException("Transport not connected");
|
||||||
|
|
||||||
using var content = new StringContent(
|
using var content = new StringContent(
|
||||||
JsonSerializer.Serialize(message, _jsonOptions),
|
JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>()),
|
||||||
Encoding.UTF8,
|
Encoding.UTF8,
|
||||||
"application/json"
|
"application/json"
|
||||||
);
|
);
|
||||||
|
|
@ -143,7 +143,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize<JsonRpcResponse>(responseContent, _jsonOptions) ??
|
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, _jsonOptions.GetTypeInfo<JsonRpcResponse>()) ??
|
||||||
throw new McpTransportException("Failed to initialize client");
|
throw new McpTransportException("Failed to initialize client");
|
||||||
|
|
||||||
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
|
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
|
||||||
|
|
@ -277,7 +277,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = JsonSerializer.Deserialize<IJsonRpcMessage>(data, _jsonOptions);
|
var message = JsonSerializer.Deserialize(data, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
_logger.TransportMessageParseUnexpectedType(EndpointName, data);
|
_logger.TransportMessageParseUnexpectedType(EndpointName, data);
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(message, _jsonOptions);
|
var json = JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
_logger.TransportSendingMessage(EndpointName, id, json);
|
_logger.TransportSendingMessage(EndpointName, id, json);
|
||||||
|
|
||||||
// Write the message followed by a newline
|
// Write the message followed by a newline
|
||||||
|
|
@ -206,7 +206,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
line=line.Trim();//Fixes an error when the service prefixes nonprintable characters
|
line=line.Trim();//Fixes an error when the service prefixes nonprintable characters
|
||||||
var message = JsonSerializer.Deserialize<IJsonRpcMessage>(line, _jsonOptions);
|
var message = JsonSerializer.Deserialize(line, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
if (message != null)
|
if (message != null)
|
||||||
{
|
{
|
||||||
string messageId = "(no id)";
|
string messageId = "(no id)";
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(message, _jsonOptions);
|
var json = JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
_logger.TransportSendingMessage(EndpointName, id, json);
|
_logger.TransportSendingMessage(EndpointName, id, json);
|
||||||
|
|
||||||
await _stdout.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false);
|
await _stdout.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
@ -166,7 +166,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = JsonSerializer.Deserialize<IJsonRpcMessage>(line, _jsonOptions);
|
var message = JsonSerializer.Deserialize(line, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
if (message != null)
|
if (message != null)
|
||||||
{
|
{
|
||||||
string messageId = "(no id)";
|
string messageId = "(no id)";
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var payload = JsonSerializer.Serialize(message);
|
var payload = JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
||||||
_logger.MessageHandlerError(EndpointName, message.GetType().Name, payload, ex);
|
_logger.MessageHandlerError(EndpointName, message.GetType().Name, payload, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +236,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
// Expensive logging, use the logging framework to check if the logger is enabled
|
// Expensive logging, use the logging framework to check if the logger is enabled
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_logger.SendingRequestPayload(EndpointName, JsonSerializer.Serialize(request));
|
_logger.SendingRequestPayload(EndpointName, JsonSerializer.Serialize(request, _jsonOptions.GetTypeInfo<JsonRpcRequest>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less expensive information logging
|
// Less expensive information logging
|
||||||
|
|
@ -256,8 +256,8 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
if (response is JsonRpcResponse success)
|
if (response is JsonRpcResponse success)
|
||||||
{
|
{
|
||||||
// Convert the Result object to JSON and back to get our strongly-typed result
|
// Convert the Result object to JSON and back to get our strongly-typed result
|
||||||
var resultJson = JsonSerializer.Serialize(success.Result, _jsonOptions);
|
var resultJson = JsonSerializer.Serialize(success.Result, _jsonOptions.GetTypeInfo<object?>());
|
||||||
var resultObject = JsonSerializer.Deserialize<TResult>(resultJson, _jsonOptions);
|
var resultObject = JsonSerializer.Deserialize(resultJson, _jsonOptions.GetTypeInfo<TResult>());
|
||||||
|
|
||||||
// Not expensive logging because we're already converting to JSON in order to get the result object
|
// Not expensive logging because we're already converting to JSON in order to get the result object
|
||||||
_logger.RequestResponseReceivedPayload(EndpointName, resultJson);
|
_logger.RequestResponseReceivedPayload(EndpointName, resultJson);
|
||||||
|
|
@ -270,7 +270,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
|
|
||||||
// Result object was null, this is unexpected
|
// Result object was null, this is unexpected
|
||||||
_logger.RequestResponseTypeConversionError(EndpointName, request.Method, typeof(TResult));
|
_logger.RequestResponseTypeConversionError(EndpointName, request.Method, typeof(TResult));
|
||||||
throw new McpClientException($"Unexpected response type {JsonSerializer.Serialize(success.Result)}, expected {typeof(TResult)}");
|
throw new McpClientException($"Unexpected response type {JsonSerializer.Serialize(success.Result, _jsonOptions.GetTypeInfo<TResult>())}, expected {typeof(TResult)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unexpected response type
|
// Unexpected response type
|
||||||
|
|
@ -295,7 +295,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_logger.SendingMessage(EndpointName, JsonSerializer.Serialize(message));
|
_logger.SendingMessage(EndpointName, JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _transport.SendMessageAsync(message, cancellationToken);
|
return _transport.SendMessageAsync(message, cancellationToken);
|
||||||
|
|
@ -339,8 +339,8 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
_requestHandlers[method] = async (request, cancellationToken) =>
|
_requestHandlers[method] = async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Convert the params JsonElement to our type using the same options
|
// Convert the params JsonElement to our type using the same options
|
||||||
var jsonString = JsonSerializer.Serialize(request.Params);
|
var jsonString = JsonSerializer.Serialize(request.Params, _jsonOptions.GetTypeInfo<object?>());
|
||||||
var typedRequest = JsonSerializer.Deserialize<TRequest>(jsonString, _jsonOptions);
|
var typedRequest = JsonSerializer.Deserialize(jsonString, _jsonOptions.GetTypeInfo<TRequest>());
|
||||||
|
|
||||||
return await handler(typedRequest, cancellationToken).ConfigureAwait(false);
|
return await handler(typedRequest, cancellationToken).ConfigureAwait(false);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
using System.Text.Json;
|
using McpDotNet.Protocol.Messages;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using McpDotNet.Protocol.Messages;
|
|
||||||
|
|
||||||
namespace McpDotNet.Utils.Json;
|
namespace McpDotNet.Utils.Json;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// JSON converter for IJsonRpcMessage that handles polymorphic deserialization of different message types.
|
/// JSON converter for IJsonRpcMessage that handles polymorphic deserialization of different message types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class JsonRpcMessageConverter : JsonConverter<IJsonRpcMessage>
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public sealed class JsonRpcMessageConverter : JsonConverter<IJsonRpcMessage>
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IJsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override IJsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
|
@ -40,13 +42,13 @@ internal sealed class JsonRpcMessageConverter : JsonConverter<IJsonRpcMessage>
|
||||||
// Messages with an error property are error responses
|
// Messages with an error property are error responses
|
||||||
if (hasError)
|
if (hasError)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<JsonRpcError>(rawText, options);
|
return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcError>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages with a result property are success responses
|
// Messages with a result property are success responses
|
||||||
if (root.TryGetProperty("result", out _))
|
if (root.TryGetProperty("result", out _))
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<JsonRpcResponse>(rawText, options);
|
return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcResponse>());
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new JsonException("Response must have either result or error");
|
throw new JsonException("Response must have either result or error");
|
||||||
|
|
@ -55,13 +57,13 @@ internal sealed class JsonRpcMessageConverter : JsonConverter<IJsonRpcMessage>
|
||||||
// Messages with a method but no id are notifications
|
// Messages with a method but no id are notifications
|
||||||
if (hasMethod && !hasId)
|
if (hasMethod && !hasId)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<JsonRpcNotification>(rawText, options);
|
return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcNotification>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages with both method and id are requests
|
// Messages with both method and id are requests
|
||||||
if (hasMethod && hasId)
|
if (hasMethod && hasId)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<JsonRpcRequest>(rawText, options);
|
return JsonSerializer.Deserialize(rawText, options.GetTypeInfo<JsonRpcRequest>());
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new JsonException("Invalid JSON-RPC message format");
|
throw new JsonException("Invalid JSON-RPC message format");
|
||||||
|
|
@ -73,16 +75,16 @@ internal sealed class JsonRpcMessageConverter : JsonConverter<IJsonRpcMessage>
|
||||||
switch (value)
|
switch (value)
|
||||||
{
|
{
|
||||||
case JsonRpcRequest request:
|
case JsonRpcRequest request:
|
||||||
JsonSerializer.Serialize(writer, request, options);
|
JsonSerializer.Serialize(writer, request, options.GetTypeInfo<JsonRpcRequest>());
|
||||||
break;
|
break;
|
||||||
case JsonRpcNotification notification:
|
case JsonRpcNotification notification:
|
||||||
JsonSerializer.Serialize(writer, notification, options);
|
JsonSerializer.Serialize(writer, notification, options.GetTypeInfo<JsonRpcNotification>());
|
||||||
break;
|
break;
|
||||||
case JsonRpcResponse response:
|
case JsonRpcResponse response:
|
||||||
JsonSerializer.Serialize(writer, response, options);
|
JsonSerializer.Serialize(writer, response, options.GetTypeInfo<JsonRpcResponse>());
|
||||||
break;
|
break;
|
||||||
case JsonRpcError error:
|
case JsonRpcError error:
|
||||||
JsonSerializer.Serialize(writer, error, options);
|
JsonSerializer.Serialize(writer, error, options.GetTypeInfo<JsonRpcError>());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new JsonException($"Unknown JSON-RPC message type: {value.GetType()}");
|
throw new JsonException($"Unknown JSON-RPC message type: {value.GetType()}");
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
using System.Text.Json;
|
using McpDotNet.Protocol.Messages;
|
||||||
|
using McpDotNet.Protocol.Types;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace McpDotNet.Utils.Json;
|
namespace McpDotNet.Utils.Json;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extensions for configuring System.Text.Json serialization options for MCP.
|
/// Extensions for configuring System.Text.Json serialization options for MCP.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class JsonSerializerOptionsExtensions
|
internal static partial class JsonSerializerOptionsExtensions
|
||||||
{
|
{
|
||||||
public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
|
public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
|
||||||
|
|
||||||
|
|
@ -14,19 +19,54 @@ internal static class JsonSerializerOptionsExtensions
|
||||||
/// Creates default options to use for MCP-related serialization.
|
/// Creates default options to use for MCP-related serialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The configured options.</returns>
|
/// <returns>The configured options.</returns>
|
||||||
|
[UnconditionalSuppressMessage("AotAnalysis", "IL3050", Justification = "DefaultJsonTypeInfoResolver is only used when reflection-based serialization is enabled")]
|
||||||
|
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "DefaultJsonTypeInfoResolver is only used when reflection-based serialization is enabled")]
|
||||||
private static JsonSerializerOptions CreateDefaultOptions()
|
private static JsonSerializerOptions CreateDefaultOptions()
|
||||||
{
|
{
|
||||||
JsonSerializerOptions options = new();
|
// If reflection-based serialization is enabled by default, use it, as it's the most permissive in terms of what it can serialize,
|
||||||
|
// and we want to be flexible in terms of what can be put into the various collections in the object model.
|
||||||
|
// Otherwise, use the source-generated options to enable trimming and Native AOT.
|
||||||
|
|
||||||
// Add custom converters
|
if (JsonSerializer.IsReflectionEnabledByDefault)
|
||||||
options.Converters.Add(new JsonRpcMessageConverter());
|
{
|
||||||
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
|
// Keep in sync with the JsonSourceGenerationOptions attribute on JsonContext below.
|
||||||
|
JsonSerializerOptions options = new(JsonSerializerDefaults.Web)
|
||||||
|
{
|
||||||
|
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||||
|
Converters = { new JsonStringEnumConverter() },
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
||||||
|
};
|
||||||
|
|
||||||
// Configure general options
|
options.MakeReadOnly();
|
||||||
options.PropertyNameCaseInsensitive = true;
|
return options;
|
||||||
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
}
|
||||||
options.NumberHandling = JsonNumberHandling.AllowReadingFromString;
|
|
||||||
|
|
||||||
return options;
|
return JsonContext.Default.Options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
|
||||||
|
(JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
|
||||||
|
|
||||||
|
// Keep in sync with CreateDefaultOptions above.
|
||||||
|
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
|
||||||
|
UseStringEnumConverter = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
NumberHandling = JsonNumberHandling.AllowReadingFromString)]
|
||||||
|
[JsonSerializable(typeof(JsonDocument))]
|
||||||
|
[JsonSerializable(typeof(JsonElement))]
|
||||||
|
[JsonSerializable(typeof(JsonNode))]
|
||||||
|
[JsonSerializable(typeof(IJsonRpcMessage))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcRequest))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcNotification))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcResponse))]
|
||||||
|
[JsonSerializable(typeof(JsonRpcError))]
|
||||||
|
[JsonSerializable(typeof(ServerCapabilities))]
|
||||||
|
[JsonSerializable(typeof(Implementation))]
|
||||||
|
[JsonSerializable(typeof(CreateMessageResult))]
|
||||||
|
[JsonSerializable(typeof(ListRootsResult))]
|
||||||
|
[JsonSerializable(typeof(InitializeResult))]
|
||||||
|
[JsonSerializable(typeof(JsonSchema))]
|
||||||
|
[JsonSerializable(typeof(CallToolResponse))]
|
||||||
|
internal sealed partial class JsonContext : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
<AnalysisMode>All</AnalysisMode>
|
<AnalysisMode>All</AnalysisMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
|
||||||
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- license and package properties -->
|
<!-- license and package properties -->
|
||||||
<PackageId>mcpdotnet</PackageId>
|
<PackageId>mcpdotnet</PackageId>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
using McpDotNet.Protocol.Types;
|
using McpDotNet.Protocol.Types;
|
||||||
using McpDotNet.Server;
|
using McpDotNet.Server;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ public class McpServerBuilderExtensionsToolsTests
|
||||||
|
|
||||||
var tool = result.Tools.First(t => t.Name == "TestTool");
|
var tool = result.Tools.First(t => t.Name == "TestTool");
|
||||||
Assert.Equal("TestTool", tool.Name);
|
Assert.Equal("TestTool", tool.Name);
|
||||||
Assert.Empty(tool.Description);
|
Assert.Empty(tool.Description!);
|
||||||
Assert.NotNull(tool.InputSchema);
|
Assert.NotNull(tool.InputSchema);
|
||||||
Assert.Equal("object", tool.InputSchema.Type);
|
Assert.Equal("object", tool.InputSchema.Type);
|
||||||
Assert.NotNull(tool.InputSchema.Properties);
|
Assert.NotNull(tool.InputSchema.Properties);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue