diff --git a/src/Common/Experimentals.cs b/src/Common/Experimentals.cs new file mode 100644 index 000000000..c81ef981e --- /dev/null +++ b/src/Common/Experimentals.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; + +namespace ModelContextProtocol; + +/// +/// Defines diagnostic IDs, Messages, and Urls for APIs annotated with . +/// +/// +/// When an experimental API is associated with an experimental specification, the message +/// should refer to the specification version that introduces the feature and the SEP +/// when available. If there is a SEP associated with the experimental API, the Url should +/// point to the SEP issue. +/// +/// Experimental diagnostic IDs are in the format MCP5###. +/// +/// +/// Diagnostic IDs cannot be reused when experimental API are removed or promoted to stable. +/// This ensures that users do not suppress warnings for new diagnostics with existing +/// suppressions that might be left in place from prior uses of the same diagnostic ID. +/// +/// +internal static class Experimentals +{ + // public const string Tasks_DiagnosticId = "MCP5001"; + // public const string Tasks_Message = "The Tasks feature is experimental within specification version 2025-11-25 and is subject to change. See SEP-1686 for more information."; + // public const string Tasks_Url = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686"; +} diff --git a/src/Common/Obsoletions.cs b/src/Common/Obsoletions.cs new file mode 100644 index 000000000..ae7581997 --- /dev/null +++ b/src/Common/Obsoletions.cs @@ -0,0 +1,25 @@ +namespace ModelContextProtocol; + +/// +/// Defines diagnostic IDs, Messages, and Urls for APIs annotated with . +/// +/// +/// When a deprecated API is associated with an specification change, the message +/// should refer to the specification version that introduces the change and the SEP +/// when available. If there is a SEP associated with the experimental API, the Url should +/// point to the SEP issue. +/// +/// Obsolete diagnostic IDs are in the format MCP9###. +/// +/// +/// Diagnostic IDs cannot be reused when obsolete APIs are removed or restored. +/// This ensures that users do not suppress warnings for new diagnostics with existing +/// suppressions that might be left in place from prior uses of the same diagnostic ID. +/// +/// +internal static class Obsoletions +{ + public const string LegacyTitledEnumSchema_DiagnosticId = "MCP9001"; + public const string LegacyTitledEnumSchema_Message = "The EnumSchema and LegacyTitledEnumSchema APIs are deprecated as of specification version 2025-11-25 and will be removed in a future major version. See SEP-1330 for more information."; + public const string LegacyTitledEnumSchema_Url = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330"; +} diff --git a/src/Common/Polyfills/System/Diagnostics/CodeAnalysis/ExperimentalAttribute.cs b/src/Common/Polyfills/System/Diagnostics/CodeAnalysis/ExperimentalAttribute.cs new file mode 100644 index 000000000..3e5cea280 --- /dev/null +++ b/src/Common/Polyfills/System/Diagnostics/CodeAnalysis/ExperimentalAttribute.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that an API is experimental and it may change in the future. +/// +/// +/// This attribute allows call sites to be flagged with a diagnostic that indicates that an experimental +/// feature is used. Authors can use this attribute to ship preview features in their assemblies. +/// +[AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Module | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Enum | + AttributeTargets.Constructor | + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Event | + AttributeTargets.Interface | + AttributeTargets.Delegate, Inherited = false)] +internal sealed class ExperimentalAttribute : Attribute +{ + /// + /// Initializes a new instance of the class, specifying the ID that the compiler will use + /// when reporting a use of the API the attribute applies to. + /// + /// The ID that the compiler will use when reporting a use of the API the attribute applies to. + public ExperimentalAttribute(string diagnosticId) + { + DiagnosticId = diagnosticId; + } + + /// + /// Gets the ID that the compiler will use when reporting a use of the API the attribute applies to. + /// + /// The unique diagnostic ID. + /// + /// The diagnostic ID is shown in build output for warnings and errors. + /// This property represents the unique ID that can be used to suppress the warnings or errors, if needed. + /// + public string DiagnosticId { get; } + + /// + /// Gets or sets an optional message associated with the experimental attribute. + /// + /// The message that provides additional information about the experimental feature. + /// + /// This message can be used to provide more context or guidance about the experimental feature. + /// + public string? Message { get; set; } + + /// + /// Gets or sets the URL for corresponding documentation. + /// The API accepts a format string instead of an actual URL, creating a generic URL that includes the diagnostic ID. + /// + /// The format string that represents a URL to corresponding documentation. + /// An example format string is https://contoso.com/obsoletion-warnings/{0}. + public string? UrlFormat { get; set; } +} +#endif diff --git a/src/Common/Polyfills/System/ObsoleteAttribute.cs b/src/Common/Polyfills/System/ObsoleteAttribute.cs new file mode 100644 index 000000000..02f9b5297 --- /dev/null +++ b/src/Common/Polyfills/System/ObsoleteAttribute.cs @@ -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. + +#if !NET +namespace System; + +/// +/// Marks program elements that are no longer in use. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | + AttributeTargets.Interface | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate, + Inherited = false)] +internal sealed class ObsoleteAttribute : Attribute +{ + public ObsoleteAttribute() + { + } + + public ObsoleteAttribute(string? message) + { + Message = message; + } + + public ObsoleteAttribute(string? message, bool error) + { + Message = message; + IsError = error; + } + + public string? Message { get; } + + public bool IsError { get; } + + public string? DiagnosticId { get; set; } + + public string? UrlFormat { get; set; } +} +#endif diff --git a/src/ModelContextProtocol.Analyzers/Diagnostics.cs b/src/ModelContextProtocol.Analyzers/Diagnostics.cs index 99f868f88..d8d8ead18 100644 --- a/src/ModelContextProtocol.Analyzers/Diagnostics.cs +++ b/src/ModelContextProtocol.Analyzers/Diagnostics.cs @@ -9,6 +9,14 @@ namespace ModelContextProtocol.Analyzers; /// Provides the diagnostic descriptors used by the assembly. +/// +/// Analyzer diagnostic IDs are in the format MCP### (or MCP1### if ever needed). +/// +/// Diagnostic IDs cannot be reused if an analyzer is removed. +/// This ensures that users do not suppress warnings for new diagnostics with existing +/// suppressions that might be left in place from prior uses of the same diagnostic ID. +/// +/// internal static class Diagnostics { public static DiagnosticDescriptor InvalidXmlDocumentation { get; } = new( diff --git a/src/ModelContextProtocol.Core/Client/IMcpClient.cs b/src/ModelContextProtocol.Core/Client/IMcpClient.cs deleted file mode 100644 index 141add86a..000000000 --- a/src/ModelContextProtocol.Core/Client/IMcpClient.cs +++ /dev/null @@ -1,50 +0,0 @@ -using ModelContextProtocol.Protocol; -using System.ComponentModel; - -namespace ModelContextProtocol.Client; - -/// -/// Represents an instance of a Model Context Protocol (MCP) client that connects to and communicates with an MCP server. -/// -[Obsolete($"Use {nameof(McpClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 -[EditorBrowsable(EditorBrowsableState.Never)] -public interface IMcpClient : IMcpEndpoint -{ - /// - /// Gets the capabilities supported by the connected server. - /// - /// The client is not connected. - ServerCapabilities ServerCapabilities { get; } - - /// - /// Gets the implementation information of the connected server. - /// - /// - /// - /// This property provides identification details about the connected server, including its name and version. - /// It is populated during the initialization handshake and is available after a successful connection. - /// - /// - /// This information can be useful for logging, debugging, compatibility checks, and displaying server - /// information to users. - /// - /// - /// The client is not connected. - Implementation ServerInfo { get; } - - /// - /// Gets any instructions describing how to use the connected server and its features. - /// - /// - /// - /// This property contains instructions provided by the server during initialization that explain - /// how to effectively use its capabilities. These instructions can include details about available - /// tools, expected input formats, limitations, or any other helpful information. - /// - /// - /// This can be used by clients to improve an LLM's understanding of available tools, prompts, and resources. - /// It can be thought of like a "hint" to the model and may be added to a system prompt. - /// - /// - string? ServerInstructions { get; } -} diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs index f6545d920..b96f5ef11 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs @@ -10,9 +10,7 @@ namespace ModelContextProtocol.Client; /// /// Represents an instance of a Model Context Protocol (MCP) client session that connects to and communicates with an MCP server. /// -#pragma warning disable CS0618 // Type or member is obsolete -public abstract partial class McpClient : McpSession, IMcpClient -#pragma warning restore CS0618 // Type or member is obsolete +public abstract partial class McpClient : McpSession { /// Creates an , connecting it to the specified server. /// The transport instance used to communicate with the server. diff --git a/src/ModelContextProtocol.Core/Client/McpClient.cs b/src/ModelContextProtocol.Core/Client/McpClient.cs index f63f4b450..abd556fb6 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.cs @@ -5,9 +5,7 @@ namespace ModelContextProtocol.Client; /// /// Represents an instance of a Model Context Protocol (MCP) client session that connects to and communicates with an MCP server. /// -#pragma warning disable CS0618 // Type or member is obsolete -public abstract partial class McpClient : McpSession, IMcpClient -#pragma warning restore CS0618 // Type or member is obsolete +public abstract partial class McpClient : McpSession { /// /// Gets the capabilities supported by the connected server. diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs deleted file mode 100644 index 4f46b7492..000000000 --- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs +++ /dev/null @@ -1,608 +0,0 @@ -using Microsoft.Extensions.AI; -using ModelContextProtocol.Protocol; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Text.Json; - -namespace ModelContextProtocol.Client; - -/// -/// Provides extension methods for interacting with an . -/// -/// -/// This class contains extension methods that simplify common operations with an MCP client, -/// such as pinging a server, listing and working with tools, prompts, and resources, and -/// managing subscriptions to resources. -/// -public static class McpClientExtensions -{ - /// - /// Sends a ping request to verify server connectivity. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// A task that completes when the ping is successful. - /// - /// - /// This method is used to check if the MCP server is online and responding to requests. - /// It can be useful for health checking, ensuring the connection is established, or verifying - /// that the client has proper authorization to communicate with the server. - /// - /// - /// The ping operation is lightweight and does not require any parameters. A successful completion - /// of the task indicates that the server is operational and accessible. - /// - /// - /// is . - /// The server cannot be reached or returned an error response. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.PingAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task PingAsync(this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).PingAsync(cancellationToken); - - /// - /// Retrieves a list of available tools from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The serializer options governing tool parameter serialization. If null, the default options are used. - /// The to monitor for cancellation requests. The default is . - /// A list of all available tools as instances. - /// - /// - /// This method fetches all available tools from the MCP server and returns them as a complete list. - /// It automatically handles pagination with cursors if the server responds with only a portion per request. - /// - /// - /// For servers with a large number of tools and that responds with paginated responses, consider using - /// instead, because it streams tools as they arrive rather than loading them all at once. - /// - /// - /// The serializer options provided are flowed to each and will be used - /// when invoking tools in order to serialize any parameters. - /// - /// - /// - /// - /// // Get all tools available on the server - /// var tools = await mcpClient.ListToolsAsync(); - /// - /// // Use tools with an AI client - /// ChatOptions chatOptions = new() - /// { - /// Tools = [.. tools] - /// }; - /// - /// await foreach (var update in chatClient.GetStreamingResponseAsync(userMessage, chatOptions)) - /// { - /// Console.Write(update); - /// } - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListToolsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask> ListToolsAsync( - this IMcpClient client, - JsonSerializerOptions? serializerOptions = null, - CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListToolsAsync(serializerOptions, cancellationToken); - - /// - /// Creates an enumerable for asynchronously enumerating all available tools from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The serializer options governing tool parameter serialization. If null, the default options are used. - /// The to monitor for cancellation requests. The default is . - /// An asynchronous sequence of all available tools as instances. - /// - /// - /// This method uses asynchronous enumeration to retrieve tools from the server, which allows processing tools - /// as they arrive rather than waiting for all tools to be retrieved. The method automatically handles pagination - /// with cursors if the server responds with tools split across multiple responses. - /// - /// - /// The serializer options provided are flowed to each and will be used - /// when invoking tools to serialize any parameters. - /// - /// - /// Every iteration through the returned - /// will result in re-querying the server and yielding the sequence of available tools. - /// - /// - /// - /// - /// // Enumerate all tools available on the server - /// await foreach (var tool in client.EnumerateToolsAsync()) - /// { - /// Console.WriteLine($"Tool: {tool.Name}"); - /// } - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateToolsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static IAsyncEnumerable EnumerateToolsAsync( - this IMcpClient client, - JsonSerializerOptions? serializerOptions = null, - CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumerateToolsAsync(serializerOptions, cancellationToken); - - /// - /// Retrieves a list of available prompts from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// A list of all available prompts as instances. - /// - /// - /// This method fetches all available prompts from the MCP server and returns them as a complete list. - /// It automatically handles pagination with cursors if the server responds with only a portion per request. - /// - /// - /// For servers with a large number of prompts and that responds with paginated responses, consider using - /// instead, as it streams prompts as they arrive rather than loading them all at once. - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListPromptsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask> ListPromptsAsync( - this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListPromptsAsync(cancellationToken); - - /// - /// Creates an enumerable for asynchronously enumerating all available prompts from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// An asynchronous sequence of all available prompts as instances. - /// - /// - /// This method uses asynchronous enumeration to retrieve prompts from the server, which allows processing prompts - /// as they arrive rather than waiting for all prompts to be retrieved. The method automatically handles pagination - /// with cursors if the server responds with prompts split across multiple responses. - /// - /// - /// Every iteration through the returned - /// will result in re-querying the server and yielding the sequence of available prompts. - /// - /// - /// - /// - /// // Enumerate all prompts available on the server - /// await foreach (var prompt in client.EnumeratePromptsAsync()) - /// { - /// Console.WriteLine($"Prompt: {prompt.Name}"); - /// } - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumeratePromptsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static IAsyncEnumerable EnumeratePromptsAsync( - this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumeratePromptsAsync(cancellationToken); - - /// - /// Retrieves a specific prompt from the MCP server. - /// - /// The client instance used to communicate with the MCP server. - /// The name of the prompt to retrieve. - /// Optional arguments for the prompt. Keys are parameter names, and values are the argument values. - /// The serialization options governing argument serialization. - /// The to monitor for cancellation requests. The default is . - /// A task containing the prompt's result with content and messages. - /// - /// - /// This method sends a request to the MCP server to create the specified prompt with the provided arguments. - /// The server will process the arguments and return a prompt containing messages or other content. - /// - /// - /// Arguments are serialized into JSON and passed to the server, where they may be used to customize the - /// prompt's behavior or content. Each prompt may have different argument requirements. - /// - /// - /// The returned contains a collection of objects, - /// which can be converted to objects using the method. - /// - /// - /// Thrown when the prompt does not exist, when required arguments are missing, or when the server encounters an error processing the prompt. - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.GetPromptAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask GetPromptAsync( - this IMcpClient client, - string name, - IReadOnlyDictionary? arguments = null, - JsonSerializerOptions? serializerOptions = null, - CancellationToken cancellationToken = default) - => AsClientOrThrow(client).GetPromptAsync(name, arguments, serializerOptions, cancellationToken); - - /// - /// Retrieves a list of available resource templates from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// A list of all available resource templates as instances. - /// - /// - /// This method fetches all available resource templates from the MCP server and returns them as a complete list. - /// It automatically handles pagination with cursors if the server responds with only a portion per request. - /// - /// - /// For servers with a large number of resource templates and that responds with paginated responses, consider using - /// instead, as it streams templates as they arrive rather than loading them all at once. - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourceTemplatesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask> ListResourceTemplatesAsync( - this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListResourceTemplatesAsync(cancellationToken); - - /// - /// Creates an enumerable for asynchronously enumerating all available resource templates from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// An asynchronous sequence of all available resource templates as instances. - /// - /// - /// This method uses asynchronous enumeration to retrieve resource templates from the server, which allows processing templates - /// as they arrive rather than waiting for all templates to be retrieved. The method automatically handles pagination - /// with cursors if the server responds with templates split across multiple responses. - /// - /// - /// Every iteration through the returned - /// will result in re-querying the server and yielding the sequence of available resource templates. - /// - /// - /// - /// - /// // Enumerate all resource templates available on the server - /// await foreach (var template in client.EnumerateResourceTemplatesAsync()) - /// { - /// Console.WriteLine($"Template: {template.Name}"); - /// } - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourceTemplatesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static IAsyncEnumerable EnumerateResourceTemplatesAsync( - this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumerateResourceTemplatesAsync(cancellationToken); - - /// - /// Retrieves a list of available resources from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// A list of all available resources as instances. - /// - /// - /// This method fetches all available resources from the MCP server and returns them as a complete list. - /// It automatically handles pagination with cursors if the server responds with only a portion per request. - /// - /// - /// For servers with a large number of resources and that responds with paginated responses, consider using - /// instead, as it streams resources as they arrive rather than loading them all at once. - /// - /// - /// - /// - /// // Get all resources available on the server - /// var resources = await client.ListResourcesAsync(); - /// - /// // Display information about each resource - /// foreach (var resource in resources) - /// { - /// Console.WriteLine($"Resource URI: {resource.Uri}"); - /// } - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourcesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask> ListResourcesAsync( - this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListResourcesAsync(cancellationToken); - - /// - /// Creates an enumerable for asynchronously enumerating all available resources from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The to monitor for cancellation requests. The default is . - /// An asynchronous sequence of all available resources as instances. - /// - /// - /// This method uses asynchronous enumeration to retrieve resources from the server, which allows processing resources - /// as they arrive rather than waiting for all resources to be retrieved. The method automatically handles pagination - /// with cursors if the server responds with resources split across multiple responses. - /// - /// - /// Every iteration through the returned - /// will result in re-querying the server and yielding the sequence of available resources. - /// - /// - /// - /// - /// // Enumerate all resources available on the server - /// await foreach (var resource in client.EnumerateResourcesAsync()) - /// { - /// Console.WriteLine($"Resource URI: {resource.Uri}"); - /// } - /// - /// - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourcesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static IAsyncEnumerable EnumerateResourcesAsync( - this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumerateResourcesAsync(cancellationToken); - - /// - /// Reads a resource from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The uri of the resource. - /// The to monitor for cancellation requests. The default is . - /// is . - /// is . - /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask ReadResourceAsync( - this IMcpClient client, string uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); - - /// - /// Reads a resource from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The uri of the resource. - /// The to monitor for cancellation requests. The default is . - /// is . - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask ReadResourceAsync( - this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); - - /// - /// Reads a resource from the server. - /// - /// The client instance used to communicate with the MCP server. - /// The uri template of the resource. - /// Arguments to use to format . - /// The to monitor for cancellation requests. The default is . - /// is . - /// is . - /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask ReadResourceAsync( - this IMcpClient client, string uriTemplate, IReadOnlyDictionary arguments, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ReadResourceAsync(uriTemplate, arguments, cancellationToken); - - /// - /// Requests completion suggestions for a prompt argument or resource reference. - /// - /// The client instance used to communicate with the MCP server. - /// The reference object specifying the type and optional URI or name. - /// The name of the argument for which completions are requested. - /// The current value of the argument, used to filter relevant completions. - /// The to monitor for cancellation requests. The default is . - /// A containing completion suggestions. - /// - /// - /// This method allows clients to request auto-completion suggestions for arguments in a prompt template - /// or for resource references. - /// - /// - /// When working with prompt references, the server will return suggestions for the specified argument - /// that match or begin with the current argument value. This is useful for implementing intelligent - /// auto-completion in user interfaces. - /// - /// - /// When working with resource references, the server will return suggestions relevant to the specified - /// resource URI. - /// - /// - /// is . - /// is . - /// is . - /// is empty or composed entirely of whitespace. - /// The server returned an error response. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CompleteAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask CompleteAsync(this IMcpClient client, Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).CompleteAsync(reference, argumentName, argumentValue, cancellationToken); - - /// - /// Subscribes to a resource on the server to receive notifications when it changes. - /// - /// The client instance used to communicate with the MCP server. - /// The URI of the resource to which to subscribe. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous operation. - /// - /// - /// This method allows the client to register interest in a specific resource identified by its URI. - /// When the resource changes, the server will send notifications to the client, enabling real-time - /// updates without polling. - /// - /// - /// The subscription remains active until explicitly unsubscribed using - /// or until the client disconnects from the server. - /// - /// - /// To handle resource change notifications, register an event handler for the appropriate notification events, - /// such as with . - /// - /// - /// is . - /// is . - /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).SubscribeToResourceAsync(uri, cancellationToken); - - /// - /// Subscribes to a resource on the server to receive notifications when it changes. - /// - /// The client instance used to communicate with the MCP server. - /// The URI of the resource to which to subscribe. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous operation. - /// - /// - /// This method allows the client to register interest in a specific resource identified by its URI. - /// When the resource changes, the server will send notifications to the client, enabling real-time - /// updates without polling. - /// - /// - /// The subscription remains active until explicitly unsubscribed using - /// or until the client disconnects from the server. - /// - /// - /// To handle resource change notifications, register an event handler for the appropriate notification events, - /// such as with . - /// - /// - /// is . - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).SubscribeToResourceAsync(uri, cancellationToken); - - /// - /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. - /// - /// The client instance used to communicate with the MCP server. - /// The URI of the resource to unsubscribe from. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous operation. - /// - /// - /// This method cancels a previous subscription to a resource, stopping the client from receiving - /// notifications when that resource changes. - /// - /// - /// The unsubscribe operation is idempotent, meaning it can be called multiple times for the same - /// resource without causing errors, even if there is no active subscription. - /// - /// - /// Due to the nature of the MCP protocol, it is possible the client may receive notifications after - /// unsubscribing if those notifications were issued by the server prior to the unsubscribe request being received. - /// - /// - /// is . - /// is . - /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, cancellationToken); - - /// - /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. - /// - /// The client instance used to communicate with the MCP server. - /// The URI of the resource to unsubscribe from. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous operation. - /// - /// - /// This method cancels a previous subscription to a resource, stopping the client from receiving - /// notifications when that resource changes. - /// - /// - /// The unsubscribe operation is idempotent, meaning it can be called multiple times for the same - /// resource without causing errors, even if there is no active subscription. - /// - /// - /// Due to the nature of the MCP protocol, it is possible the client may receive notifications after - /// unsubscribing if those notifications were issued by the server prior to the unsubscribe request being received. - /// - /// - /// is . - /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, cancellationToken); - - /// - /// Invokes a tool on the server. - /// - /// The client instance used to communicate with the MCP server. - /// The name of the tool to call on the server.. - /// An optional dictionary of arguments to pass to the tool. Each key represents a parameter name, - /// and its associated value represents the argument value. - /// - /// - /// An optional to have progress notifications reported to it. Setting this to a non- - /// value will result in a progress token being included in the call, and any resulting progress notifications during the operation - /// routed to this instance. - /// - /// - /// The JSON serialization options governing argument serialization. If , the default serialization options are used. - /// - /// The to monitor for cancellation requests. The default is . - /// - /// A task containing the from the tool execution. The response includes - /// the tool's output content, which may be structured data, text, or an error message. - /// - /// is . - /// is . - /// The server could not find the requested tool, or the server encountered an error while processing the request. - /// - /// - /// // Call a simple echo tool with a string argument - /// var result = await client.CallToolAsync( - /// "echo", - /// new Dictionary<string, object?> - /// { - /// ["message"] = "Hello MCP!" - /// }); - /// - /// - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CallToolAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask CallToolAsync( - this IMcpClient client, - string toolName, - IReadOnlyDictionary? arguments = null, - IProgress? progress = null, - JsonSerializerOptions? serializerOptions = null, - CancellationToken cancellationToken = default) - => AsClientOrThrow(client).CallToolAsync(toolName, arguments, progress, serializerOptions, cancellationToken); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable CS0618 // Type or member is obsolete - private static McpClient AsClientOrThrow(IMcpClient client, [CallerMemberName] string memberName = "") -#pragma warning restore CS0618 // Type or member is obsolete - { - if (client is not McpClient mcpClient) - { - ThrowInvalidEndpointType(memberName); - } - - return mcpClient; - - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowInvalidEndpointType(string memberName) - => throw new InvalidOperationException( - $"Only arguments assignable to '{nameof(McpClient)}' are supported. " + - $"Prefer using '{nameof(McpClient)}.{memberName}' instead, as " + - $"'{nameof(McpClientExtensions)}.{memberName}' is obsolete and will be " + - $"removed in the future."); - } -} diff --git a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs b/src/ModelContextProtocol.Core/Client/McpClientFactory.cs deleted file mode 100644 index 805787256..000000000 --- a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Extensions.Logging; -using System.ComponentModel; - -namespace ModelContextProtocol.Client; - -/// -/// Provides factory methods for creating Model Context Protocol (MCP) clients. -/// -/// -/// This factory class is the primary way to instantiate instances -/// that connect to MCP servers. It handles the creation and connection -/// of appropriate implementations through the supplied transport. -/// -[Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CreateAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 -[EditorBrowsable(EditorBrowsableState.Never)] -public static partial class McpClientFactory -{ - /// Creates an , connecting it to the specified server. - /// The transport instance used to communicate with the server. - /// - /// A client configuration object which specifies client capabilities and protocol version. - /// If , details based on the current process will be employed. - /// - /// A logger factory for creating loggers for clients. - /// The to monitor for cancellation requests. The default is . - /// An that's connected to the specified server. - /// is . - /// is . - public static async Task CreateAsync( - IClientTransport clientTransport, - McpClientOptions? clientOptions = null, - ILoggerFactory? loggerFactory = null, - CancellationToken cancellationToken = default) - => await McpClient.CreateAsync(clientTransport, clientOptions, loggerFactory, cancellationToken); -} \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs index 56e12d26c..ddd4fed80 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs @@ -36,7 +36,7 @@ public class McpClientHandlers /// /// /// Handlers provided via will be registered with the client for the lifetime of the client. - /// For transient handlers, you can use to register a handler that can + /// For transient handlers, you can use to register a handler that can /// then be unregistered by disposing of the returned from the method. /// /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index ccc7b9c64..4cea0e22d 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -58,12 +58,10 @@ private void RegisterHandlers(McpClientOptions options, NotificationHandlers not { McpClientHandlers handlers = options.Handlers; -#pragma warning disable CS0618 // Type or member is obsolete - var notificationHandlersFromOptions = handlers.NotificationHandlers ?? options.Capabilities?.NotificationHandlers; - var samplingHandler = handlers.SamplingHandler ?? options.Capabilities?.Sampling?.SamplingHandler; - var rootsHandler = handlers.RootsHandler ?? options.Capabilities?.Roots?.RootsHandler; - var elicitationHandler = handlers.ElicitationHandler ?? options.Capabilities?.Elicitation?.ElicitationHandler; -#pragma warning restore CS0618 // Type or member is obsolete + var notificationHandlersFromOptions = handlers.NotificationHandlers; + var samplingHandler = handlers.SamplingHandler; + var rootsHandler = handlers.RootsHandler; + var elicitationHandler = handlers.ElicitationHandler; if (notificationHandlersFromOptions is not null) { diff --git a/src/ModelContextProtocol.Core/IMcpEndpoint.cs b/src/ModelContextProtocol.Core/IMcpEndpoint.cs deleted file mode 100644 index 40106cb07..000000000 --- a/src/ModelContextProtocol.Core/IMcpEndpoint.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.ComponentModel; -using ModelContextProtocol.Client; -using ModelContextProtocol.Protocol; -using ModelContextProtocol.Server; - -namespace ModelContextProtocol; - -/// -/// Represents a client or server Model Context Protocol (MCP) endpoint. -/// -/// -/// -/// The MCP endpoint provides the core communication functionality used by both clients and servers: -/// -/// Sending JSON-RPC requests and receiving responses. -/// Sending notifications to the connected endpoint. -/// Registering handlers for receiving notifications. -/// -/// -/// -/// serves as the base interface for both and -/// interfaces, providing the common functionality needed for MCP protocol -/// communication. Most applications will use these more specific interfaces rather than working with -/// directly. -/// -/// -/// All MCP endpoints should be properly disposed after use as they implement . -/// -/// -[Obsolete($"Use {nameof(McpSession)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 -[EditorBrowsable(EditorBrowsableState.Never)] -public interface IMcpEndpoint : IAsyncDisposable -{ - /// Gets an identifier associated with the current MCP session. - /// - /// Typically populated in transports supporting multiple sessions such as Streamable HTTP or SSE. - /// Can return if the session hasn't initialized or if the transport doesn't - /// support multiple sessions (as is the case with STDIO). - /// - string? SessionId { get; } - - /// - /// Sends a JSON-RPC request to the connected endpoint and waits for a response. - /// - /// The JSON-RPC request to send. - /// The to monitor for cancellation requests. The default is . - /// A task containing the endpoint's response. - /// The transport is not connected, or another error occurs during request processing. - /// An error occured during request processing. - /// - /// This method provides low-level access to send raw JSON-RPC requests. For most use cases, - /// consider using the strongly-typed extension methods that provide a more convenient API. - /// - Task SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default); - - /// - /// Sends a JSON-RPC message to the connected endpoint. - /// - /// - /// The JSON-RPC message to send. This can be any type that implements JsonRpcMessage, such as - /// JsonRpcRequest, JsonRpcResponse, JsonRpcNotification, or JsonRpcError. - /// - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous send operation. - /// The transport is not connected. - /// is . - /// - /// - /// This method provides low-level access to send any JSON-RPC message. For specific message types, - /// consider using the higher-level methods such as or extension methods - /// like , - /// which provide a simpler API. - /// - /// - /// The method will serialize the message and transmit it using the underlying transport mechanism. - /// - /// - Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default); - - /// Registers a handler to be invoked when a notification for the specified method is received. - /// The notification method. - /// The handler to be invoked. - /// An that will remove the registered handler when disposed. - IAsyncDisposable RegisterNotificationHandler(string method, Func handler); -} diff --git a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs deleted file mode 100644 index 160b31c00..000000000 --- a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs +++ /dev/null @@ -1,152 +0,0 @@ -using ModelContextProtocol.Client; -using ModelContextProtocol.Protocol; -using ModelContextProtocol.Server; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Text.Json; - -namespace ModelContextProtocol; - -/// -/// Provides extension methods for interacting with an . -/// -/// -/// -/// This class provides strongly typed methods for working with the Model Context Protocol (MCP) endpoints, -/// simplifying JSON-RPC communication by handling serialization and deserialization of parameters and results. -/// -/// -/// These extension methods are designed to be used with both client () and -/// server () implementations of the interface. -/// -/// -public static class McpEndpointExtensions -{ - /// - /// Sends a JSON-RPC request and attempts to deserialize the result to . - /// - /// The type of the request parameters to serialize from. - /// The type of the result to deserialize to. - /// The MCP client or server instance. - /// The JSON-RPC method name to invoke. - /// The request parameters. - /// The request ID for the request. - /// The options governing request serialization. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous operation. The task result contains the deserialized result. - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendRequestAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask SendRequestAsync( - this IMcpEndpoint endpoint, - string method, - TParameters parameters, - JsonSerializerOptions? serializerOptions = null, - RequestId requestId = default, - CancellationToken cancellationToken = default) - where TResult : notnull - => AsSessionOrThrow(endpoint).SendRequestAsync(method, parameters, serializerOptions, requestId, cancellationToken); - - /// - /// Sends a parameterless notification to the connected endpoint. - /// - /// The MCP client or server instance. - /// The notification method name. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous send operation. - /// - /// - /// This method sends a notification without any parameters. Notifications are one-way messages - /// that don't expect a response. They are commonly used for events, status updates, or to signal - /// changes in state. - /// - /// - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default) - => AsSessionOrThrow(client).SendNotificationAsync(method, cancellationToken); - - /// - /// Sends a notification with parameters to the connected endpoint. - /// - /// The type of the notification parameters to serialize. - /// The MCP client or server instance. - /// The JSON-RPC method name for the notification. - /// Object representing the notification parameters. - /// The options governing parameter serialization. If null, default options are used. - /// The to monitor for cancellation requests. The default is . - /// A task that represents the asynchronous send operation. - /// - /// - /// This method sends a notification with parameters to the connected endpoint. Notifications are one-way - /// messages that don't expect a response, commonly used for events, status updates, or signaling changes. - /// - /// - /// The parameters object is serialized to JSON according to the provided serializer options or the default - /// options if none are specified. - /// - /// - /// The Model Context Protocol defines several standard notification methods in , - /// but custom methods can also be used for application-specific notifications. - /// - /// - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task SendNotificationAsync( - this IMcpEndpoint endpoint, - string method, - TParameters parameters, - JsonSerializerOptions? serializerOptions = null, - CancellationToken cancellationToken = default) - => AsSessionOrThrow(endpoint).SendNotificationAsync(method, parameters, serializerOptions, cancellationToken); - - /// - /// Notifies the connected endpoint of progress for a long-running operation. - /// - /// The endpoint issuing the notification. - /// The identifying the operation for which progress is being reported. - /// The progress update to send, containing information such as percentage complete or status message. - /// The to monitor for cancellation requests. The default is . - /// A task representing the completion of the notification operation (not the operation being tracked). - /// is . - /// - /// - /// This method sends a progress notification to the connected endpoint using the Model Context Protocol's - /// standardized progress notification format. Progress updates are identified by a - /// that allows the recipient to correlate multiple updates with a specific long-running operation. - /// - /// - /// Progress notifications are sent asynchronously and don't block the operation from continuing. - /// - /// - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.NotifyProgressAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task NotifyProgressAsync( - this IMcpEndpoint endpoint, - ProgressToken progressToken, - ProgressNotificationValue progress, - CancellationToken cancellationToken = default) - => AsSessionOrThrow(endpoint).NotifyProgressAsync(progressToken, progress, cancellationToken); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable CS0618 // Type or member is obsolete - private static McpSession AsSessionOrThrow(IMcpEndpoint endpoint, [CallerMemberName] string memberName = "") -#pragma warning restore CS0618 // Type or member is obsolete - { - if (endpoint is not McpSession session) - { - ThrowInvalidEndpointType(memberName); - } - - return session; - - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowInvalidEndpointType(string memberName) - => throw new InvalidOperationException( - $"Only arguments assignable to '{nameof(McpSession)}' are supported. " + - $"Prefer using '{nameof(McpServer)}.{memberName}' instead, as " + - $"'{nameof(McpEndpointExtensions)}.{memberName}' is obsolete and will be " + - $"removed in the future."); - } -} diff --git a/src/ModelContextProtocol.Core/McpSession.Methods.cs b/src/ModelContextProtocol.Core/McpSession.Methods.cs index be3128cb1..79a6e1a0b 100644 --- a/src/ModelContextProtocol.Core/McpSession.Methods.cs +++ b/src/ModelContextProtocol.Core/McpSession.Methods.cs @@ -5,9 +5,7 @@ namespace ModelContextProtocol; -#pragma warning disable CS0618 // Type or member is obsolete -public abstract partial class McpSession : IMcpEndpoint, IAsyncDisposable -#pragma warning restore CS0618 // Type or member is obsolete +public abstract partial class McpSession : IAsyncDisposable { /// /// Sends a JSON-RPC request and attempts to deserialize the result to . diff --git a/src/ModelContextProtocol.Core/McpSession.cs b/src/ModelContextProtocol.Core/McpSession.cs index acc6df5cd..b0469aec0 100644 --- a/src/ModelContextProtocol.Core/McpSession.cs +++ b/src/ModelContextProtocol.Core/McpSession.cs @@ -26,9 +26,7 @@ namespace ModelContextProtocol; /// All MCP sessions should be properly disposed after use as they implement . /// /// -#pragma warning disable CS0618 // Type or member is obsolete -public abstract partial class McpSession : IMcpEndpoint, IAsyncDisposable -#pragma warning restore CS0618 // Type or member is obsolete +public abstract partial class McpSession : IAsyncDisposable { /// Gets an identifier associated with the current MCP session. /// diff --git a/src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj b/src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj index cdbe25a2d..5cd8339bf 100644 --- a/src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj +++ b/src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj @@ -14,8 +14,15 @@ true + + + $(NoWarn);CS0436 + + + + diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs index d4a803085..87a631f95 100644 --- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs @@ -66,26 +66,4 @@ public sealed class ClientCapabilities /// [JsonPropertyName("elicitation")] public ElicitationCapability? Elicitation { get; set; } - - /// Gets or sets notification handlers to register with the client. - /// - /// - /// When constructed, the client will enumerate these handlers, which may contain multiple handlers per notification method key, once. - /// The client will not re-enumerate the sequence after initialization. - /// - /// - /// Notification handlers allow the client to respond to server-sent notifications for specific methods. - /// Each key in the collection is a notification method name, and each value is a callback that will be invoked - /// when a notification with that method is received. - /// - /// - /// Handlers provided via will be registered with the client for the lifetime of the client. - /// For transient handlers, can be used to register a handler that can - /// then be unregistered by disposing of the returned from the method. - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.NotificationHandlers)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable>>? NotificationHandlers { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs index 17f028947..8da6e5fc9 100644 --- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs @@ -28,16 +28,4 @@ namespace ModelContextProtocol.Protocol; /// public sealed class CompletionsCapability { - /// - /// Gets or sets the handler for completion requests. - /// - /// - /// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol. - /// The handler receives a reference type (e.g., "ref/prompt" or "ref/resource") and the current argument value, - /// and should return appropriate completion suggestions. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.CompleteHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? CompleteHandler { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitRequestParams.cs b/src/ModelContextProtocol.Core/Protocol/ElicitRequestParams.cs index aabd4e4d9..95f8084d0 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitRequestParams.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitRequestParams.cs @@ -247,10 +247,12 @@ public class Converter : JsonConverter { if (enumNames is not null) { - // EnumSchema for backward compatibility -#pragma warning disable CS0618 // Type or member is obsolete + // EnumSchema is deprecated but supported for backward compatibility. + // Use the EnumSchema class, which is an alias for LegacyTitledEnumSchema, + // to ensure backward compatibility with existing code relying on that type. +#pragma warning disable MCP9001 psd = new EnumSchema -#pragma warning restore CS0618 // Type or member is obsolete +#pragma warning restore MCP9001 { Enum = enumValues, EnumNames = enumNames, @@ -558,22 +560,22 @@ public override void Write(Utf8JsonWriter writer, PrimitiveSchemaDefinition valu } break; -#pragma warning disable CS0618 // Type or member is obsolete - case LegacyTitledEnumSchema legacyEnum: -#pragma warning restore CS0618 // Type or member is obsolete - if (legacyEnum.Enum is not null) +#pragma warning disable MCP9001 // LegacyTitledEnumSchema is deprecated but supported for backward compatibility + case LegacyTitledEnumSchema legacyTitledEnum: +#pragma warning restore MCP9001 + if (legacyTitledEnum.Enum is not null) { writer.WritePropertyName("enum"); - JsonSerializer.Serialize(writer, legacyEnum.Enum, McpJsonUtilities.JsonContext.Default.IListString); + JsonSerializer.Serialize(writer, legacyTitledEnum.Enum, McpJsonUtilities.JsonContext.Default.IListString); } - if (legacyEnum.EnumNames is not null) + if (legacyTitledEnum.EnumNames is not null) { writer.WritePropertyName("enumNames"); - JsonSerializer.Serialize(writer, legacyEnum.EnumNames, McpJsonUtilities.JsonContext.Default.IListString); + JsonSerializer.Serialize(writer, legacyTitledEnum.EnumNames, McpJsonUtilities.JsonContext.Default.IListString); } - if (legacyEnum.Default is not null) + if (legacyTitledEnum.Default is not null) { - writer.WriteString("default", legacyEnum.Default); + writer.WriteString("default", legacyTitledEnum.Default); } break; @@ -740,51 +742,6 @@ public override string Type public bool? Default { get; set; } } - /// - /// Represents a legacy schema for an enum type with enumNames. - /// - /// - /// This schema is deprecated in favor of . - /// - [Obsolete("Use TitledSingleSelectEnumSchema instead. This type will be removed in a future version.")] - public class LegacyTitledEnumSchema : PrimitiveSchemaDefinition - { - /// - [JsonPropertyName("type")] - public override string Type - { - get => "string"; - set - { - if (value is not "string") - { - throw new ArgumentException("Type must be 'string'.", nameof(value)); - } - } - } - - /// Gets or sets the list of allowed string values for the enum. - [JsonPropertyName("enum")] - [field: MaybeNull] - public IList Enum - { - get => field ??= []; - set - { - Throw.IfNull(value); - field = value; - } - } - - /// Gets or sets optional display names corresponding to the enum values. - [JsonPropertyName("enumNames")] - public IList? EnumNames { get; set; } - - /// Gets or sets the default value for the enum. - [JsonPropertyName("default")] - public string? Default { get; set; } - } - /// /// Represents a schema for single-selection enumeration without display titles for options. /// @@ -970,10 +927,59 @@ public override string Type } /// - /// Represents a schema for an enum type. This is a compatibility alias for . + /// Represents a legacy schema for an enum type with enumNames. + /// This is a compatibility alias for . /// - [Obsolete("Use UntitledSingleSelectEnumSchema or TitledSingleSelectEnumSchema instead. This type will be removed in a future version.")] + /// + /// This schema is deprecated in favor of . + /// + [Obsolete(Obsoletions.LegacyTitledEnumSchema_Message, DiagnosticId = Obsoletions.LegacyTitledEnumSchema_DiagnosticId, UrlFormat = Obsoletions.LegacyTitledEnumSchema_Url)] public sealed class EnumSchema : LegacyTitledEnumSchema { } + + /// + /// Represents a legacy schema for an enum type with enumNames. + /// + /// + /// This schema is deprecated in favor of . + /// + [Obsolete(Obsoletions.LegacyTitledEnumSchema_Message, DiagnosticId = Obsoletions.LegacyTitledEnumSchema_DiagnosticId, UrlFormat = Obsoletions.LegacyTitledEnumSchema_Url)] + public class LegacyTitledEnumSchema : PrimitiveSchemaDefinition + { + /// + [JsonPropertyName("type")] + public override string Type + { + get => "string"; + set + { + if (value is not "string") + { + throw new ArgumentException("Type must be 'string'.", nameof(value)); + } + } + } + + /// Gets or sets the list of allowed string values for the enum. + [JsonPropertyName("enum")] + [field: MaybeNull] + public IList Enum + { + get => field ??= []; + set + { + Throw.IfNull(value); + field = value; + } + } + + /// Gets or sets optional display names corresponding to the enum values. + [JsonPropertyName("enumNames")] + public IList? EnumNames { get; set; } + + /// Gets or sets the default value for the enum. + [JsonPropertyName("default")] + public string? Default { get; set; } + } } diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs index 9d46bcc43..3fdf5ddcb 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs @@ -23,21 +23,4 @@ namespace ModelContextProtocol.Protocol; /// public sealed class ElicitationCapability { - /// - /// Gets or sets the handler for processing requests. - /// - /// - /// - /// This handler function is called when an MCP server requests the client to provide additional - /// information during interactions. The client must set this property for the elicitation capability to work. - /// - /// - /// The handler receives message parameters and a cancellation token. - /// It should return a containing the response to the elicitation request. - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.ElicitationHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public Func>? ElicitationHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs index c166a223a..ac8f03f95 100644 --- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs @@ -20,11 +20,4 @@ namespace ModelContextProtocol.Protocol; /// public sealed class LoggingCapability { - /// - /// Gets or sets the handler for set logging level requests from clients. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.SetLoggingLevelHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? SetLoggingLevelHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs index cc6d823cd..4a830c48e 100644 --- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs @@ -31,59 +31,4 @@ public sealed class PromptsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is invoked when a client requests a list of available prompts from the server - /// via a request. Results from this handler are returned - /// along with any prompts defined in . - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListPromptsHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? ListPromptsHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// - /// This handler is invoked when a client requests details for a specific prompt by name and provides arguments - /// for the prompt if needed. The handler receives the request context containing the prompt name and any arguments, - /// and should return a with the prompt messages and other details. - /// - /// - /// This handler will be invoked if the requested prompt name is not found in the , - /// allowing for dynamic prompt generation or retrieval from external sources. - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.GetPromptHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? GetPromptHandler { get; set; } - - /// - /// Gets or sets a collection of prompts that will be served by the server. - /// - /// - /// - /// The contains the predefined prompts that clients can request from the server. - /// This collection works in conjunction with and - /// when those are provided: - /// - /// - /// - For requests: The server returns all prompts from this collection - /// plus any additional prompts provided by the if it's set. - /// - /// - /// - For requests: The server first checks this collection for the requested prompt. - /// If not found, it will invoke the as a fallback if one is set. - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.PromptCollection)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpServerPrimitiveCollection? PromptCollection { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs index 7cda1a62a..d7b8f6e44 100644 --- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs @@ -29,92 +29,4 @@ public sealed class ResourcesCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is called when clients request available resource templates that can be used - /// to create resources within the Model Context Protocol server. - /// Resource templates define the structure and URI patterns for resources accessible in the system, - /// allowing clients to discover available resource types and their access patterns. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourceTemplatesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? ListResourceTemplatesHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler responds to client requests for available resources and returns information about resources accessible through the server. - /// The implementation should return a with the matching resources. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourcesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? ListResourcesHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is responsible for retrieving the content of a specific resource identified by its URI in the Model Context Protocol. - /// When a client sends a resources/read request, this handler is invoked with the resource URI. - /// The handler should implement logic to locate and retrieve the requested resource, then return - /// its contents in a ReadResourceResult object. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ReadResourceHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? ReadResourceHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// When a client sends a request, this handler is invoked with the resource URI - /// to be subscribed to. The implementation should register the client's interest in receiving updates - /// for the specified resource. - /// Subscriptions allow clients to receive real-time notifications when resources change, without - /// requiring polling. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.SubscribeToResourcesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? SubscribeToResourcesHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// When a client sends a request, this handler is invoked with the resource URI - /// to be unsubscribed from. The implementation should remove the client's registration for receiving updates - /// about the specified resource. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.UnsubscribeFromResourcesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? UnsubscribeFromResourcesHandler { get; set; } - - /// - /// Gets or sets a collection of resources served by the server. - /// - /// - /// - /// Resources specified via augment the , - /// and handlers, if provided. Resources with template expressions in their URI templates are considered resource templates - /// and are listed via ListResourceTemplate, whereas resources without template parameters are considered static resources and are listed with ListResources. - /// - /// - /// ReadResource requests will first check the for the exact resource being requested. If no match is found, they'll proceed to - /// try to match the resource against each resource template in . If no match is still found, the request will fall back to - /// any handler registered for . - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.ResourceCollection)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpServerResourceCollection? ResourceCollection { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs index 582e7f98c..4739cde4a 100644 --- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs @@ -33,16 +33,4 @@ public sealed class RootsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is invoked when a client sends a request to retrieve available roots. - /// The handler receives request parameters and should return a containing the collection of available roots. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.RootsHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public Func>? RootsHandler { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs index aba24a77c..cb530e795 100644 --- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs @@ -19,29 +19,6 @@ namespace ModelContextProtocol.Protocol; /// public sealed class SamplingCapability { - /// - /// Gets or sets the handler for processing requests. - /// - /// - /// - /// This handler function is called when an MCP server requests the client to generate content - /// using an AI model. The client must set this property for the sampling capability to work. - /// - /// - /// The handler receives message parameters, a progress reporter for updates, and a - /// cancellation token. It should return a containing the - /// generated content. - /// - /// - /// You can create a handler using the extension - /// method with any implementation of . - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.SamplingHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public Func, CancellationToken, ValueTask>? SamplingHandler { get; set; } - /// /// Gets or sets whether the client supports context inclusion via includeContext parameter. /// @@ -57,4 +34,3 @@ public sealed class SamplingCapability [JsonPropertyName("tools")] public SamplingToolsCapability? Tools { get; set; } } - diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs index 5dda8da9f..86a823396 100644 --- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs @@ -65,26 +65,4 @@ public sealed class ServerCapabilities /// [JsonPropertyName("completions")] public CompletionsCapability? Completions { get; set; } - - /// Gets or sets notification handlers to register with the server. - /// - /// - /// When constructed, the server will enumerate these handlers once, which may contain multiple handlers per notification method key. - /// The server will not re-enumerate the sequence after initialization. - /// - /// - /// Notification handlers allow the server to respond to client-sent notifications for specific methods. - /// Each key in the collection is a notification method name, and each value is a callback that will be invoked - /// when a notification with that method is received. - /// - /// - /// Handlers provided via will be registered with the server for the lifetime of the server. - /// For transient handlers, may be used to register a handler that can - /// then be unregistered by disposing of the returned from the method. - /// - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.NotificationHandlers)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable>>? NotificationHandlers { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs index de8ee3654..b06c2f123 100644 --- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs @@ -22,49 +22,4 @@ public sealed class ToolsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// The handler should return a list of available tools when requested by a client. - /// It supports pagination through the cursor mechanism, where the client can make - /// repeated calls with the cursor returned by the previous call to retrieve more tools. - /// When used in conjunction with , both the tools from this handler - /// and the tools from the collection will be combined to form the complete list of available tools. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListToolsHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? ListToolsHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is invoked when a client makes a call to a tool that isn't found in the . - /// The handler should implement logic to execute the requested tool and return appropriate results. - /// It receives a containing information about the tool - /// being called and its arguments, and should return a with the execution results. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.CallToolHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpRequestHandler? CallToolHandler { get; set; } - - /// - /// Gets or sets a collection of tools served by the server. - /// - /// - /// Tools will specified via augment the and - /// , if provided. ListTools requests will output information about every tool - /// in and then also any tools output by , if it's - /// non-. CallTool requests will first check for the tool - /// being requested, and if the tool is not found in the , any specified - /// will be invoked as a fallback. - /// - [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.ToolCollection)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public McpServerPrimitiveCollection? ToolCollection { get; set; } } diff --git a/src/ModelContextProtocol.Core/Server/IMcpServer.cs b/src/ModelContextProtocol.Core/Server/IMcpServer.cs deleted file mode 100644 index 8b88aa7a2..000000000 --- a/src/ModelContextProtocol.Core/Server/IMcpServer.cs +++ /dev/null @@ -1,65 +0,0 @@ -using ModelContextProtocol.Protocol; -using System.ComponentModel; - -namespace ModelContextProtocol.Server; - -/// -/// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client. -/// -[Obsolete($"Use {nameof(McpServer)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 -[EditorBrowsable(EditorBrowsableState.Never)] -public interface IMcpServer : IMcpEndpoint -{ - /// - /// Gets the capabilities supported by the client. - /// - /// - /// - /// These capabilities are established during the initialization handshake and indicate - /// which features the client supports, such as sampling, roots, and other - /// protocol-specific functionality. - /// - /// - /// Server implementations can check these capabilities to determine which features - /// are available when interacting with the client. - /// - /// - ClientCapabilities? ClientCapabilities { get; } - - /// - /// Gets the version and implementation information of the connected client. - /// - /// - /// - /// This property contains identification information about the client that has connected to this server, - /// including its name and version. This information is provided by the client during initialization. - /// - /// - /// Server implementations can use this information for logging, tracking client versions, - /// or implementing client-specific behaviors. - /// - /// - Implementation? ClientInfo { get; } - - /// - /// Gets the options used to construct this server. - /// - /// - /// These options define the server's capabilities, protocol version, and other configuration - /// settings that were used to initialize the server. - /// - McpServerOptions ServerOptions { get; } - - /// - /// Gets the service provider for the server. - /// - IServiceProvider? Services { get; } - - /// Gets the last logging level set by the client, or if it's never been set. - LoggingLevel? LoggingLevel { get; } - - /// - /// Runs the server, listening for and handling client requests. - /// - Task RunAsync(CancellationToken cancellationToken = default); -} diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs index 604f21583..f8b7b7781 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs @@ -14,9 +14,7 @@ namespace ModelContextProtocol.Server; /// /// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client. /// -#pragma warning disable CS0618 // Type or member is obsolete -public abstract partial class McpServer : McpSession, IMcpServer -#pragma warning restore CS0618 // Type or member is obsolete +public abstract partial class McpServer : McpSession { /// /// Caches request schemas for elicitation requests based on the type and serializer options. diff --git a/src/ModelContextProtocol.Core/Server/McpServer.cs b/src/ModelContextProtocol.Core/Server/McpServer.cs index 02c17de1a..2d8ea6826 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.cs @@ -5,9 +5,7 @@ namespace ModelContextProtocol.Server; /// /// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client. /// -#pragma warning disable CS0618 // Type or member is obsolete -public abstract partial class McpServer : McpSession, IMcpServer -#pragma warning restore CS0618 // Type or member is obsolete +public abstract partial class McpServer : McpSession { /// /// Gets the capabilities supported by the client. diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs deleted file mode 100644 index b20865576..000000000 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ /dev/null @@ -1,138 +0,0 @@ -using Microsoft.Extensions.AI; -using Microsoft.Extensions.Logging; -using ModelContextProtocol.Protocol; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Text.Json; - -namespace ModelContextProtocol.Server; - -/// -/// Provides extension methods for interacting with an instance. -/// -public static class McpServerExtensions -{ - /// - /// Requests to sample an LLM via the client using the specified request parameters. - /// - /// The server instance initiating the request. - /// The parameters for the sampling request. - /// The to monitor for cancellation requests. - /// A task containing the sampling result from the client. - /// is . - /// The client does not support sampling. - /// - /// This method requires the client to support sampling capabilities. - /// It allows detailed control over sampling parameters including messages, system prompt, temperature, - /// and token limits. - /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask SampleAsync( - this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).SampleAsync(request, cancellationToken); - - /// - /// Requests to sample an LLM via the client using the provided chat messages and options. - /// - /// The server initiating the request. - /// The messages to send as part of the request. - /// The options to use for the request, including model parameters and constraints. - /// The to monitor for cancellation requests. The default is . - /// A task containing the chat response from the model. - /// is . - /// is . - /// The client does not support sampling. - /// - /// This method converts the provided chat messages into a format suitable for the sampling API, - /// handling different content types such as text, images, and audio. - /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static Task SampleAsync( - this IMcpServer server, - IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).SampleAsync(messages, options, cancellationToken); - - /// - /// Creates an wrapper that can be used to send sampling requests to the client. - /// - /// The server to be wrapped as an . - /// The that can be used to issue sampling requests to the client. - /// is . - /// The client does not support sampling. - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static IChatClient AsSamplingChatClient(this IMcpServer server) - => AsServerOrThrow(server).AsSamplingChatClient(); - - /// Gets an on which logged messages will be sent as notifications to the client. - /// The server to wrap as an . - /// An that can be used to log to the client.. - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) - => AsServerOrThrow(server).AsClientLoggerProvider(); - - /// - /// Requests the client to list the roots it exposes. - /// - /// The server initiating the request. - /// The parameters for the list roots request. - /// The to monitor for cancellation requests. - /// A task containing the list of roots exposed by the client. - /// is . - /// The client does not support roots. - /// - /// This method requires the client to support the roots capability. - /// Root resources allow clients to expose a hierarchical structure of resources that can be - /// navigated and accessed by the server. These resources might include file systems, databases, - /// or other structured data sources that the client makes available through the protocol. - /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.RequestRootsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask RequestRootsAsync( - this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).RequestRootsAsync(request, cancellationToken); - - /// - /// Requests additional information from the user via the client, allowing the server to elicit structured data. - /// - /// The server initiating the request. - /// The parameters for the elicitation request. - /// The to monitor for cancellation requests. - /// A task containing the elicitation result. - /// is . - /// The client does not support elicitation. - /// - /// This method requires the client to support the elicitation capability. - /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.ElicitAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 - [EditorBrowsable(EditorBrowsableState.Never)] - public static ValueTask ElicitAsync( - this IMcpServer server, ElicitRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).ElicitAsync(request, cancellationToken); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable CS0618 // Type or member is obsolete - private static McpServer AsServerOrThrow(IMcpServer server, [CallerMemberName] string memberName = "") -#pragma warning restore CS0618 // Type or member is obsolete - { - if (server is not McpServer mcpServer) - { - ThrowInvalidSessionType(memberName); - } - - return mcpServer; - - [DoesNotReturn] - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowInvalidSessionType(string memberName) - => throw new InvalidOperationException( - $"Only arguments assignable to '{nameof(McpServer)}' are supported. " + - $"Prefer using '{nameof(McpServer)}.{memberName}' instead, as " + - $"'{nameof(McpServerExtensions)}.{memberName}' is obsolete and will be " + - $"removed in the future."); - } -} diff --git a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs b/src/ModelContextProtocol.Core/Server/McpServerFactory.cs deleted file mode 100644 index 7a6609d0d..000000000 --- a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.Extensions.Logging; -using ModelContextProtocol.Protocol; -using System.ComponentModel; - -namespace ModelContextProtocol.Server; - -/// -/// Provides a factory for creating instances. -/// -/// -/// This is the recommended way to create instances. -/// The factory handles proper initialization of server instances with the required dependencies. -/// -[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.Create)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 -[EditorBrowsable(EditorBrowsableState.Never)] -public static class McpServerFactory -{ - /// - /// Creates a new instance of an . - /// - /// Transport to use for the server representing an already-established MCP session. - /// Configuration options for this server, including capabilities. - /// Logger factory to use for logging. If null, logging will be disabled. - /// Optional service provider to create new instances of tools and other dependencies. - /// An instance that should be disposed when no longer needed. - /// is . - /// is . - public static IMcpServer Create( - ITransport transport, - McpServerOptions serverOptions, - ILoggerFactory? loggerFactory = null, - IServiceProvider? serviceProvider = null) - => McpServer.Create(transport, serverOptions, loggerFactory, serviceProvider); -} diff --git a/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs b/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs index 77102ac7e..6dbcea8af 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs @@ -169,7 +169,7 @@ public sealed class McpServerHandlers /// /// /// Handlers provided via will be registered with the server for the lifetime of the server. - /// For transient handlers, may be used to register a handler that can + /// For transient handlers, may be used to register a handler that can /// then be unregistered by disposing of the returned from the method. /// /// diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 41408c22b..706578d9b 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -230,10 +230,6 @@ private void ConfigureCompletion(McpServerOptions options) var completeHandler = options.Handlers.CompleteHandler; var completionsCapability = options.Capabilities?.Completions; -#pragma warning disable CS0618 // Type or member is obsolete - completeHandler ??= completionsCapability?.CompleteHandler; -#pragma warning restore CS0618 // Type or member is obsolete - if (completeHandler is null && completionsCapability is null) { return; @@ -266,14 +262,6 @@ private void ConfigureResources(McpServerOptions options) var resources = options.ResourceCollection; var resourcesCapability = options.Capabilities?.Resources; -#pragma warning disable CS0618 // Type or member is obsolete - listResourcesHandler ??= resourcesCapability?.ListResourcesHandler; - listResourceTemplatesHandler ??= resourcesCapability?.ListResourceTemplatesHandler; - readResourceHandler ??= resourcesCapability?.ReadResourceHandler; - subscribeHandler ??= resourcesCapability?.SubscribeToResourcesHandler; - unsubscribeHandler ??= resourcesCapability?.UnsubscribeFromResourcesHandler; -#pragma warning restore CS0618 // Type or member is obsolete - if (listResourcesHandler is null && listResourceTemplatesHandler is null && readResourceHandler is null && subscribeHandler is null && unsubscribeHandler is null && resources is null && resourcesCapability is null) @@ -427,11 +415,6 @@ private void ConfigurePrompts(McpServerOptions options) var prompts = options.PromptCollection; var promptsCapability = options.Capabilities?.Prompts; -#pragma warning disable CS0618 // Type or member is obsolete - listPromptsHandler ??= promptsCapability?.ListPromptsHandler; - getPromptHandler ??= promptsCapability?.GetPromptHandler; -#pragma warning restore CS0618 // Type or member is obsolete - if (listPromptsHandler is null && getPromptHandler is null && prompts is null && promptsCapability is null) { @@ -515,11 +498,6 @@ private void ConfigureTools(McpServerOptions options) var tools = options.ToolCollection; var toolsCapability = options.Capabilities?.Tools; -#pragma warning disable CS0618 // Type or member is obsolete - listToolsHandler ??= toolsCapability?.ListToolsHandler; - callToolHandler ??= toolsCapability?.CallToolHandler; -#pragma warning restore CS0618 // Type or member is obsolete - if (listToolsHandler is null && callToolHandler is null && tools is null && toolsCapability is null) { @@ -618,10 +596,6 @@ private void ConfigureLogging(McpServerOptions options) // We don't require that the handler be provided, as we always store the provided log level to the server. var setLoggingLevelHandler = options.Handlers.SetLoggingLevelHandler; -#pragma warning disable CS0618 // Type or member is obsolete - setLoggingLevelHandler ??= options.Capabilities?.Logging?.SetLoggingLevelHandler; -#pragma warning restore CS0618 // Type or member is obsolete - // Apply filters to the handler if (setLoggingLevelHandler is not null) { diff --git a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs index 9359ea157..86f0a6884 100644 --- a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs +++ b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs @@ -19,18 +19,13 @@ internal sealed class RequestServiceProvider(RequestContext serviceType == typeof(RequestContext) || serviceType == typeof(McpServer) || -#pragma warning disable CS0618 // Type or member is obsolete - serviceType == typeof(IMcpServer) || -#pragma warning restore CS0618 // Type or member is obsolete serviceType == typeof(IProgress) || serviceType == typeof(ClaimsPrincipal); /// public object? GetService(Type serviceType) => serviceType == typeof(RequestContext) ? request : -#pragma warning disable CS0618 // Type or member is obsolete - serviceType == typeof(McpServer) || serviceType == typeof(IMcpServer) ? request.Server : -#pragma warning restore CS0618 // Type or member is obsolete + serviceType == typeof(McpServer) ? request.Server : serviceType == typeof(IProgress) ? (request.Params?.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) : serviceType == typeof(ClaimsPrincipal) ? request.User : diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs deleted file mode 100644 index 5a2816003..000000000 --- a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs +++ /dev/null @@ -1,394 +0,0 @@ -using ModelContextProtocol.Client; -using ModelContextProtocol.Protocol; -using Moq; -using System.Text.Json; - -namespace ModelContextProtocol.Tests; - -#pragma warning disable CS0618 // Type or member is obsolete - -public class McpClientExtensionsTests -{ - [Fact] - public async Task PingAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.PingAsync(TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.PingAsync' instead", ex.Message); - } - - [Fact] - public async Task GetPromptAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.GetPromptAsync( - "name", cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.GetPromptAsync' instead", ex.Message); - } - - [Fact] - public async Task CallToolAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.CallToolAsync( - "tool", cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.CallToolAsync' instead", ex.Message); - } - - [Fact] - public async Task ListResourcesAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ListResourcesAsync( - cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ListResourcesAsync' instead", ex.Message); - } - - [Fact] - public void EnumerateResourcesAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = Assert.Throws(() => client.EnumerateResourcesAsync(cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.EnumerateResourcesAsync' instead", ex.Message); - } - - [Fact] - public async Task SubscribeToResourceAsync_String_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.SubscribeToResourceAsync( - "mcp://resource/1", TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.SubscribeToResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task SubscribeToResourceAsync_Uri_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.SubscribeToResourceAsync( - new Uri("mcp://resource/1"), TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.SubscribeToResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task UnsubscribeFromResourceAsync_String_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.UnsubscribeFromResourceAsync( - "mcp://resource/1", TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.UnsubscribeFromResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task UnsubscribeFromResourceAsync_Uri_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.UnsubscribeFromResourceAsync( - new Uri("mcp://resource/1"), TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.UnsubscribeFromResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task ReadResourceAsync_String_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync( - "mcp://resource/1", TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task ReadResourceAsync_Uri_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync( - new Uri("mcp://resource/1"), TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task ReadResourceAsync_Template_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync( - "mcp://resource/{id}", new Dictionary { ["id"] = 1 }, TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message); - } - - [Fact] - public async Task CompleteAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - var reference = new PromptReference { Name = "prompt" }; - - var ex = await Assert.ThrowsAsync(async () => await client.CompleteAsync( - reference, "arg", "val", TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.CompleteAsync' instead", ex.Message); - } - - [Fact] - public async Task ListToolsAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ListToolsAsync( - cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ListToolsAsync' instead", ex.Message); - } - - [Fact] - public void EnumerateToolsAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = Assert.Throws(() => client.EnumerateToolsAsync(cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.EnumerateToolsAsync' instead", ex.Message); - } - - [Fact] - public async Task ListPromptsAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ListPromptsAsync( - cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ListPromptsAsync' instead", ex.Message); - } - - [Fact] - public void EnumeratePromptsAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = Assert.Throws(() => client.EnumeratePromptsAsync(cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.EnumeratePromptsAsync' instead", ex.Message); - } - - [Fact] - public async Task ListResourceTemplatesAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await client.ListResourceTemplatesAsync( - cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.ListResourceTemplatesAsync' instead", ex.Message); - } - - [Fact] - public void EnumerateResourceTemplatesAsync_Throws_When_Not_McpClient() - { - var client = new Mock(MockBehavior.Strict).Object; - - var ex = Assert.Throws(() => client.EnumerateResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpClient.EnumerateResourceTemplatesAsync' instead", ex.Message); - } - - [Fact] - public async Task PingAsync_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(new object(), McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - await client.PingAsync(TestContext.Current.CancellationToken); - - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task GetPromptAsync_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - var resultPayload = new GetPromptResult - { - Messages = [new() - { - Role = Role.User, - Content = new TextContentBlock { Text = "hi" } - }] - }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - var result = await client.GetPromptAsync("name", cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal("hi", Assert.IsType(result.Messages[0].Content).Text); - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task CallToolAsync_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - var callResult = new CallToolResult { Content = [new TextContentBlock { Text = "ok" }] }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(callResult, McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - var result = await client.CallToolAsync("tool", cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal("ok", Assert.IsType(result.Content[0]).Text); - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task SubscribeToResourceAsync_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(new EmptyResult(), McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - await client.SubscribeToResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken); - - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task UnsubscribeFromResourceAsync_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(new EmptyResult(), McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - await client.UnsubscribeFromResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken); - - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task CompleteAsync_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - var completion = new Completion { Values = ["one", "two"] }; - var resultPayload = new CompleteResult { Completion = completion }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - var result = await client.CompleteAsync(new PromptReference { Name = "p" }, "arg", "val", TestContext.Current.CancellationToken); - - Assert.Contains("one", result.Completion.Values); - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task ReadResourceAsync_String_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - var resultPayload = new ReadResourceResult { Contents = [] }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - var result = await client.ReadResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken); - - Assert.NotNull(result); - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task ReadResourceAsync_Uri_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - var resultPayload = new ReadResourceResult { Contents = [] }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - var result = await client.ReadResourceAsync(new Uri("mcp://resource/1"), TestContext.Current.CancellationToken); - - Assert.NotNull(result); - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task ReadResourceAsync_Template_Forwards_To_McpClient_SendRequestAsync() - { - var mockClient = new Mock { CallBase = true }; - - var resultPayload = new ReadResourceResult { Contents = [] }; - - mockClient - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpClient client = mockClient.Object; - - var result = await client.ReadResourceAsync("mcp://resource/{id}", new Dictionary { ["id"] = 1 }, TestContext.Current.CancellationToken); - - Assert.NotNull(result); - mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } -} diff --git a/tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs b/tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs deleted file mode 100644 index 613c703c3..000000000 --- a/tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -using ModelContextProtocol.Protocol; -using Moq; -using System.Text.Json; - -namespace ModelContextProtocol.Tests; - -#pragma warning disable CS0618 // Type or member is obsolete - -public class McpEndpointExtensionsTests -{ - [Fact] - public async Task SendRequestAsync_Generic_Throws_When_Not_McpSession() - { - var endpoint = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.SendRequestAsync( - endpoint, "method", "param", cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.SendRequestAsync' instead", ex.Message); - } - - [Fact] - public async Task SendNotificationAsync_Parameterless_Throws_When_Not_McpSession() - { - var endpoint = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.SendNotificationAsync( - endpoint, "notify", cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.SendNotificationAsync' instead", ex.Message); - } - - [Fact] - public async Task SendNotificationAsync_Generic_Throws_When_Not_McpSession() - { - var endpoint = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.SendNotificationAsync( - endpoint, "notify", "payload", cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.SendNotificationAsync' instead", ex.Message); - } - - [Fact] - public async Task NotifyProgressAsync_Throws_When_Not_McpSession() - { - var endpoint = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.NotifyProgressAsync( - endpoint, new ProgressToken("t1"), new ProgressNotificationValue { Progress = 0.5f }, cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.NotifyProgressAsync' instead", ex.Message); - } - - [Fact] - public async Task SendRequestAsync_Generic_Forwards_To_McpSession_SendRequestAsync() - { - var mockSession = new Mock { CallBase = true }; - - mockSession - .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(42, McpJsonUtilities.DefaultOptions), - }); - - IMcpEndpoint endpoint = mockSession.Object; - - var result = await endpoint.SendRequestAsync("method", "param", cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal(42, result); - mockSession.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task SendNotificationAsync_Parameterless_Forwards_To_McpSession_SendMessageAsync() - { - var mockSession = new Mock { CallBase = true }; - - mockSession - .Setup(s => s.SendMessageAsync(It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask); - - IMcpEndpoint endpoint = mockSession.Object; - - await endpoint.SendNotificationAsync("notify", cancellationToken: TestContext.Current.CancellationToken); - - mockSession.Verify(s => s.SendMessageAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task SendNotificationAsync_Generic_Forwards_To_McpSession_SendMessageAsync() - { - var mockSession = new Mock { CallBase = true }; - - mockSession - .Setup(s => s.SendMessageAsync(It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask); - - IMcpEndpoint endpoint = mockSession.Object; - - await endpoint.SendNotificationAsync("notify", "payload", cancellationToken: TestContext.Current.CancellationToken); - - mockSession.Verify(s => s.SendMessageAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task NotifyProgressAsync_Forwards_To_McpSession_SendMessageAsync() - { - var mockSession = new Mock { CallBase = true }; - - mockSession - .Setup(s => s.SendMessageAsync(It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask); - - IMcpEndpoint endpoint = mockSession.Object; - - await endpoint.NotifyProgressAsync(new ProgressToken("progress-token"), new ProgressNotificationValue { Progress = 1 }, cancellationToken: TestContext.Current.CancellationToken); - - mockSession.Verify(s => s.SendMessageAsync(It.IsAny(), It.IsAny()), Times.Once); - } -} \ No newline at end of file diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationDefaultValuesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationDefaultValuesTests.cs index c29f7d033..5bf7ddb40 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationDefaultValuesTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationDefaultValuesTests.cs @@ -1,14 +1,12 @@ using ModelContextProtocol.Protocol; using System.Text.Json; -#pragma warning disable CS0618 // Type or member is obsolete - namespace ModelContextProtocol.Tests.Protocol; -public class ElicitationDefaultValuesTests +public static class ElicitationDefaultValuesTests { [Fact] - public void StringSchema_Default_Serializes_Correctly() + public static void StringSchema_Default_Serializes_Correctly() { // Arrange var schema = new ElicitRequestParams.StringSchema @@ -32,7 +30,7 @@ public void StringSchema_Default_Serializes_Correctly() } [Fact] - public void StringSchema_Default_Null_DoesNotSerialize() + public static void StringSchema_Default_Null_DoesNotSerialize() { // Arrange var schema = new ElicitRequestParams.StringSchema @@ -48,7 +46,7 @@ public void StringSchema_Default_Null_DoesNotSerialize() } [Fact] - public void NumberSchema_Default_Serializes_Correctly() + public static void NumberSchema_Default_Serializes_Correctly() { // Arrange var schema = new ElicitRequestParams.NumberSchema @@ -74,7 +72,7 @@ public void NumberSchema_Default_Serializes_Correctly() } [Fact] - public void NumberSchema_Integer_Default_Serializes_Correctly() + public static void NumberSchema_Integer_Default_Serializes_Correctly() { // Arrange var schema = new ElicitRequestParams.NumberSchema @@ -97,7 +95,7 @@ public void NumberSchema_Integer_Default_Serializes_Correctly() } [Fact] - public void NumberSchema_Default_Null_DoesNotSerialize() + public static void NumberSchema_Default_Null_DoesNotSerialize() { // Arrange var schema = new ElicitRequestParams.NumberSchema @@ -113,50 +111,7 @@ public void NumberSchema_Default_Null_DoesNotSerialize() } [Fact] - public void EnumSchema_Default_Serializes_Correctly() - { - // Arrange - var schema = new ElicitRequestParams.EnumSchema - { - Title = "Priority", - Description = "Task priority", - Enum = ["low", "medium", "high"], - EnumNames = ["Low Priority", "Medium Priority", "High Priority"], - Default = "medium" - }; - - // Act - serialize as base type to use the converter - string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); - var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); - - // Assert - Assert.NotNull(deserialized); - var enumSchema = Assert.IsType(deserialized); - Assert.Equal("medium", enumSchema.Default); - Assert.Equal("Priority", enumSchema.Title); - Assert.Equal("Task priority", enumSchema.Description); - Assert.Contains("\"default\":\"medium\"", json); - } - - [Fact] - public void EnumSchema_Default_Null_DoesNotSerialize() - { - // Arrange - var schema = new ElicitRequestParams.EnumSchema - { - Title = "Priority", - Enum = ["low", "medium", "high"] - }; - - // Act - serialize as base type to use the converter - string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); - - // Assert - Assert.DoesNotContain("\"default\"", json); - } - - [Fact] - public void BooleanSchema_Default_True_Serializes_Correctly() + public static void BooleanSchema_Default_True_Serializes_Correctly() { // Arrange var schema = new ElicitRequestParams.BooleanSchema @@ -178,7 +133,7 @@ public void BooleanSchema_Default_True_Serializes_Correctly() } [Fact] - public void BooleanSchema_Default_False_Serializes_Correctly() + public static void BooleanSchema_Default_False_Serializes_Correctly() { // Arrange var schema = new ElicitRequestParams.BooleanSchema @@ -199,7 +154,7 @@ public void BooleanSchema_Default_False_Serializes_Correctly() } [Fact] - public void PrimitiveSchemaDefinition_StringSchema_WithDefault_RoundTrips() + public static void PrimitiveSchemaDefinition_StringSchema_WithDefault_RoundTrips() { // Arrange var schema = new ElicitRequestParams.StringSchema @@ -221,7 +176,7 @@ public void PrimitiveSchemaDefinition_StringSchema_WithDefault_RoundTrips() } [Fact] - public void PrimitiveSchemaDefinition_NumberSchema_WithDefault_RoundTrips() + public static void PrimitiveSchemaDefinition_NumberSchema_WithDefault_RoundTrips() { // Arrange var schema = new ElicitRequestParams.NumberSchema @@ -245,30 +200,110 @@ public void PrimitiveSchemaDefinition_NumberSchema_WithDefault_RoundTrips() } [Fact] - public void PrimitiveSchemaDefinition_EnumSchema_WithDefault_RoundTrips() + public static void UntitledSingleSelectEnumSchema_Default_Null_DoesNotSerialize() { // Arrange - var schema = new ElicitRequestParams.EnumSchema + var schema = new ElicitRequestParams.UntitledSingleSelectEnumSchema { - Title = "Status", - Enum = ["draft", "published", "archived"], - Default = "draft" + Title = "Priority", + Enum = ["low", "medium", "high"] }; - // Act - serialize as base type to test the converter + // Act - serialize as base type to use the converter string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); - var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); // Assert - Assert.NotNull(deserialized); - // EnumSchema without enumNames deserializes as UntitledSingleSelectEnumSchema - var enumSchema = Assert.IsType(deserialized); - Assert.Equal("draft", enumSchema.Default); - Assert.Equal(["draft", "published", "archived"], enumSchema.Enum); + Assert.DoesNotContain("\"default\"", json); + } + + [Fact] + public static void TitledSingleSelectEnumSchema_Default_Null_DoesNotSerialize() + { + // Arrange + var schema = new ElicitRequestParams.TitledSingleSelectEnumSchema + { + Title = "Priority", + OneOf = + { + new ElicitRequestParams.EnumSchemaOption { Title = "Low", Const = "low" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Medium", Const = "medium" }, + new ElicitRequestParams.EnumSchemaOption { Title = "High", Const = "high" } + } + }; + + // Act - serialize as base type to use the converter + string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.DoesNotContain("\"default\"", json); + } + + [Fact] + public static void UntitledMultiSelectEnumSchema_Default_Null_DoesNotSerialize() + { + // Arrange + var schema = new ElicitRequestParams.UntitledMultiSelectEnumSchema + { + Title = "Tags", + Items = new ElicitRequestParams.UntitledEnumItemsSchema + { + Enum = ["tag1", "tag2", "tag3"] + } + }; + + // Act - serialize as base type to use the converter + string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.DoesNotContain("\"default\"", json); + } + + [Fact] + public static void TitledMultiSelectEnumSchema_Default_Null_DoesNotSerialize() + { + // Arrange + var schema = new ElicitRequestParams.TitledMultiSelectEnumSchema + { + Title = "Tags", + Items = new ElicitRequestParams.TitledEnumItemsSchema + { + AnyOf = + [ + new ElicitRequestParams.EnumSchemaOption { Title = "Tag 1", Const = "tag1" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Tag 2", Const = "tag2" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Tag 3", Const = "tag3" } + ] + } + }; + // Act - serialize as base type to use the converter + string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.DoesNotContain("\"default\"", json); } +#pragma warning disable MCP9001 // LegacyTitledEnumSchema is deprecated but supported for backward compatibility [Fact] - public void RequestSchema_WithAllDefaultTypes_Serializes_Correctly() + public static void LegacyTitledEnumSchema_Default_Null_DoesNotSerialize() + { + // Arrange + var schema = new ElicitRequestParams.LegacyTitledEnumSchema + { + Title = "Legacy Options", + Enum = ["option1", "option2"], + EnumNames = ["Option 1", "Option 2"] + }; + + // Act - serialize as base type to use the converter + string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.DoesNotContain("\"default\"", json); + } +#pragma warning restore MCP9001 + + [Fact] + public static void RequestSchema_WithAllDefaultTypes_Serializes_Correctly() { // Arrange var requestParams = new ElicitRequestParams @@ -299,11 +334,46 @@ public void RequestSchema_WithAllDefaultTypes_Serializes_Correctly() Title = "Active", Default = true }, - ["status"] = new ElicitRequestParams.EnumSchema + ["status"] = new ElicitRequestParams.UntitledSingleSelectEnumSchema { Title = "Status", Enum = ["active", "inactive"], Default = "active" + }, + ["tags"] = new ElicitRequestParams.UntitledMultiSelectEnumSchema + { + Title = "Tags", + Items = new ElicitRequestParams.UntitledEnumItemsSchema + { + Enum = ["tag1", "tag2", "tag3"] + }, + Default = ["tag1", "tag3"] + }, + ["salutation"] = new ElicitRequestParams.TitledSingleSelectEnumSchema + { + Title = "Salutation", + OneOf = + { + new ElicitRequestParams.EnumSchemaOption { Title = "N/A", Const = "none" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Mr.", Const = "mr" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Ms.", Const = "ms" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Dr.", Const = "dr" } + }, + Default = "none" + }, + ["categories"] = new ElicitRequestParams.TitledMultiSelectEnumSchema + { + Title = "Categories", + Items = new ElicitRequestParams.TitledEnumItemsSchema + { + AnyOf = + [ + new ElicitRequestParams.EnumSchemaOption { Title = "Category 1", Const = "cat1" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Category 2", Const = "cat2" }, + new ElicitRequestParams.EnumSchemaOption { Title = "Category 3", Const = "cat3" } + ] + }, + Default = ["cat2", "cat3"] } } } @@ -315,22 +385,30 @@ public void RequestSchema_WithAllDefaultTypes_Serializes_Correctly() // Assert Assert.NotNull(deserialized); - Assert.Equal(5, deserialized.RequestedSchema.Properties.Count); - + Assert.Equal(8, deserialized.RequestedSchema.Properties.Count); + var nameSchema = Assert.IsType(deserialized.RequestedSchema.Properties["name"]); Assert.Equal("John Doe", nameSchema.Default); - + var ageSchema = Assert.IsType(deserialized.RequestedSchema.Properties["age"]); Assert.Equal(30, ageSchema.Default); - + var scoreSchema = Assert.IsType(deserialized.RequestedSchema.Properties["score"]); Assert.Equal(85.5, scoreSchema.Default); - + var activeSchema = Assert.IsType(deserialized.RequestedSchema.Properties["active"]); Assert.True(activeSchema.Default); - - // EnumSchema without enumNames deserializes as UntitledSingleSelectEnumSchema + var statusSchema = Assert.IsType(deserialized.RequestedSchema.Properties["status"]); Assert.Equal("active", statusSchema.Default); + + var tagsSchema = Assert.IsType(deserialized.RequestedSchema.Properties["tags"]); + Assert.Equal(new List { "tag1", "tag3" }, tagsSchema.Default); + + var salutationSchema = Assert.IsType(deserialized.RequestedSchema.Properties["salutation"]); + Assert.Equal("none", salutationSchema.Default); + + var categoriesSchema = Assert.IsType(deserialized.RequestedSchema.Properties["categories"]); + Assert.Equal(new List { "cat2", "cat3" }, categoriesSchema.Default); } } diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs index 92db42f72..781b281e5 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs @@ -3,8 +3,6 @@ using ModelContextProtocol.Protocol; using System.Text.Json; -#pragma warning disable CS0618 // Type or member is obsolete - namespace ModelContextProtocol.Tests.Configuration; public partial class ElicitationTests : ClientServerTestBase @@ -46,10 +44,14 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer Description = "description4", Default = true, }, - ["prop4"] = new ElicitRequestParams.EnumSchema + ["prop4"] = new ElicitRequestParams.TitledSingleSelectEnumSchema { - Enum = ["option1", "option2", "option3"], - EnumNames = ["Name1", "Name2", "Name3"], + OneOf = + [ + new ElicitRequestParams.EnumSchemaOption { Const = "option1", Title = "Name1" }, + new ElicitRequestParams.EnumSchemaOption { Const = "option2", Title = "Name2" }, + new ElicitRequestParams.EnumSchemaOption { Const = "option3", Title = "Name3" }, + ] }, }, }, @@ -104,9 +106,9 @@ public async Task Can_Elicit_Information() break; case "prop4": - var primitiveEnum = Assert.IsType(entry.Value); - Assert.Equal(["option1", "option2", "option3"], primitiveEnum.Enum); - Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.EnumNames); + var primitiveEnum = Assert.IsType(entry.Value); + Assert.Equal(["option1", "option2", "option3"], primitiveEnum.OneOf.Select(e => e.Const)); + Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.OneOf.Select(e => e.Title)); break; default: diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs index ab0a5d4f4..737c5d627 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs @@ -4,8 +4,6 @@ using System.Text.Json; using System.Text.Json.Serialization; -#pragma warning disable CS0618 // Type or member is obsolete - namespace ModelContextProtocol.Tests.Configuration; public partial class ElicitationTypedTests : ClientServerTestBase diff --git a/tests/ModelContextProtocol.Tests/Protocol/EnumSchemaTests.cs b/tests/ModelContextProtocol.Tests/Protocol/EnumSchemaTests.cs index 86f0be591..f0addb9d7 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/EnumSchemaTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/EnumSchemaTests.cs @@ -1,8 +1,6 @@ using ModelContextProtocol.Protocol; using System.Text.Json; -#pragma warning disable CS0618 // Type or member is obsolete - namespace ModelContextProtocol.Tests.Protocol; public class EnumSchemaTests @@ -161,36 +159,9 @@ public void TitledMultiSelectEnumSchema_Serializes_Correctly() } [Fact] - public void LegacyTitledEnumSchema_WithEnumNames_Deserializes_As_EnumSchema() + public void SingleSelectEnum_WithEnum_Deserializes_As_UntitledSingleSelect() { - // Arrange - JSON with enumNames should deserialize as EnumSchema for backward compatibility - string json = """ - { - "type": "string", - "title": "Status", - "enum": ["active", "inactive", "pending"], - "enumNames": ["Active", "Inactive", "Pending"], - "default": "active" - } - """; - - // Act - var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); - - // Assert - Assert.NotNull(deserialized); - var result = Assert.IsType(deserialized); - Assert.Equal("string", result.Type); - Assert.Equal("Status", result.Title); - Assert.Equal(["active", "inactive", "pending"], result.Enum); - Assert.Equal(["Active", "Inactive", "Pending"], result.EnumNames); - Assert.Equal("active", result.Default); - } - - [Fact] - public void EnumSchema_WithoutEnumNames_Deserializes_As_UntitledSingleSelect() - { - // Arrange - JSON without enumNames should deserialize as UntitledSingleSelectEnumSchema + // Arrange - JSON with enum should deserialize as UntitledSingleSelectEnumSchema string json = """ { "type": "string", @@ -213,7 +184,7 @@ public void EnumSchema_WithoutEnumNames_Deserializes_As_UntitledSingleSelect() } [Fact] - public void EnumSchema_WithOneOf_Deserializes_As_TitledSingleSelect() + public void SingleSelectEnum_WithOneOf_Deserializes_As_TitledSingleSelect() { // Arrange - JSON with oneOf should deserialize as TitledSingleSelectEnumSchema string json = """ @@ -246,7 +217,7 @@ public void EnumSchema_WithOneOf_Deserializes_As_TitledSingleSelect() [Fact] public void MultiSelectEnum_WithEnum_Deserializes_As_UntitledMultiSelect() { - // Arrange + // Arrange - JSON with items.enum should deserialize as UntitledMultiSelectEnumSchema string json = """ { "type": "array", @@ -275,7 +246,7 @@ public void MultiSelectEnum_WithEnum_Deserializes_As_UntitledMultiSelect() [Fact] public void MultiSelectEnum_WithAnyOf_Deserializes_As_TitledMultiSelect() { - // Arrange + // Arrange - JSON with items.anyOf should deserialize as TitledMultiSelectEnumSchema string json = """ { "type": "array", @@ -306,4 +277,92 @@ public void MultiSelectEnum_WithAnyOf_Deserializes_As_TitledMultiSelect() Assert.Equal("Administrator", result.Items.AnyOf[0].Title); Assert.Equal(["user"], result.Default); } + +#pragma warning disable MCP9001 // EnumSchema and LegacyTitledEnumSchema are deprecated but supported for backward compatibility + [Fact] + public void LegacyTitledEnumSchema_Serializes_Correctly() + { + // Arrange + var schema = new ElicitRequestParams.LegacyTitledEnumSchema + { + Title = "Environment", + Description = "Deployment environment", + Enum = ["dev", "staging", "prod"], + EnumNames = ["Development", "Staging", "Production"], + Default = "staging" + }; + + // Act + string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); + var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.NotNull(deserialized); + var result = Assert.IsType(deserialized); + Assert.Equal("string", result.Type); + Assert.Equal("Environment", result.Title); + Assert.Equal("Deployment environment", result.Description); + Assert.Equal(["dev", "staging", "prod"], result.Enum); + Assert.Equal(["Development", "Staging", "Production"], result.EnumNames); + Assert.Equal("staging", result.Default); + Assert.Contains("\"enumNames\":[\"Development\",\"Staging\",\"Production\"]", json); + } + + [Fact] + public void EnumSchema_Serializes_Correctly() + { + // Arrange + var schema = new ElicitRequestParams.EnumSchema + { + Title = "Environment", + Description = "Deployment environment", + Enum = ["dev", "staging", "prod"], + EnumNames = ["Development", "Staging", "Production"], + Default = "staging" + }; + + // Act + string json = JsonSerializer.Serialize(schema, McpJsonUtilities.DefaultOptions); + var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.NotNull(deserialized); + var result = Assert.IsType(deserialized); + Assert.Equal("string", result.Type); + Assert.Equal("Environment", result.Title); + Assert.Equal("Deployment environment", result.Description); + Assert.Equal(["dev", "staging", "prod"], result.Enum); + Assert.Equal(["Development", "Staging", "Production"], result.EnumNames); + Assert.Equal("staging", result.Default); + Assert.Contains("\"enumNames\":[\"Development\",\"Staging\",\"Production\"]", json); + } + + [Fact] + public void Enum_WithEnumNames_Deserializes_As_EnumSchema() + { + // Arrange - JSON with enumNames should deserialize as (deprecated) EnumSchema + string json = """ + { + "type": "string", + "title": "Environment", + "description": "Deployment environment", + "enum": ["dev", "staging", "prod"], + "enumNames": ["Development", "Staging", "Production"], + "default": "staging" + } + """; + // Act + var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.NotNull(deserialized); + var result = Assert.IsType(deserialized); + Assert.Equal("string", result.Type); + Assert.Equal("Environment", result.Title); + Assert.Equal("Deployment environment", result.Description); + Assert.Equal(["dev", "staging", "prod"], result.Enum); + Assert.Equal(["Development", "Staging", "Production"], result.EnumNames); + Assert.Equal("staging", result.Default); + } +#pragma warning restore MCP9001 } diff --git a/tests/ModelContextProtocol.Tests/Protocol/PrimitiveSchemaDefinitionTests.cs b/tests/ModelContextProtocol.Tests/Protocol/PrimitiveSchemaDefinitionTests.cs index 1e577f965..fc83ee5ed 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/PrimitiveSchemaDefinitionTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/PrimitiveSchemaDefinitionTests.cs @@ -1,74 +1,66 @@ using ModelContextProtocol.Protocol; using System.Text.Json; -#pragma warning disable CS0618 // Type or member is obsolete - namespace ModelContextProtocol.Tests.Protocol; public static class PrimitiveSchemaDefinitionTests { [Fact] - public static void StringSchema_UnknownArrayProperty_IsIgnored() + public static void StringSchema_UnknownProperties_AreIgnored() { - // This test verifies that the PrimitiveSchemaDefinition.Converter properly skips unknown properties - // even when they contain complex structures like arrays or objects. - // - // In this unexpected JSON, "unknownArray" appears inside a string schema (where it doesn't belong). - // The converter should gracefully ignore this unknown property and successfully deserialize - // the rest of the schema. - - const string jsonWithUnknownArray = """ + const string json = """ { "type": "string", - "title": "Test String", - "minLength": 1, - "maxLength": 100, - "unknownArray": [ - { - "nested": "value" - }, - { - "another": "object" - } - ] + "title": "Test", + + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + + "minLength": 5, + "maxLength": 50, + "format": "email" } """; var result = JsonSerializer.Deserialize( - jsonWithUnknownArray, + json, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var stringSchema = Assert.IsType(result); Assert.Equal("string", stringSchema.Type); - Assert.Equal("Test String", stringSchema.Title); - Assert.Equal(1, stringSchema.MinLength); - Assert.Equal(100, stringSchema.MaxLength); + Assert.Equal("Test", stringSchema.Title); + Assert.Equal(5, stringSchema.MinLength); + Assert.Equal(50, stringSchema.MaxLength); + Assert.Equal("email", stringSchema.Format); } [Fact] - public static void NumberSchema_UnknownObjectProperty_IsIgnored() + public static void NumberSchema_UnknownProperties_AreIgnored() { - // Test that unknown properties with nested objects are properly skipped - - const string jsonWithUnknownObject = """ + const string json = """ { "type": "number", "description": "Test Number", + + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + "minimum": 0, - "maximum": 1000, - "unknownObject": { - "deeply": { - "nested": { - "value": "should be ignored" - } - } - } + "maximum": 1000 } """; var result = JsonSerializer.Deserialize( - jsonWithUnknownObject, + json, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); @@ -80,26 +72,26 @@ public static void NumberSchema_UnknownObjectProperty_IsIgnored() } [Fact] - public static void BooleanSchema_UnknownMixedProperties_AreIgnored() + public static void BooleanSchema_UnknownProperties_AreIgnored() { - // Test multiple unknown properties with different types - - const string jsonWithMixedUnknown = """ + const string json = """ { "type": "boolean", "title": "Test Boolean", - "unknownString": "value", - "unknownNumber": 42, - "unknownArray": [1, 2, 3], - "unknownObject": {"key": "value"}, - "unknownBool": true, + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + "default": false } """; var result = JsonSerializer.Deserialize( - jsonWithMixedUnknown, + json, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); @@ -110,265 +102,194 @@ public static void BooleanSchema_UnknownMixedProperties_AreIgnored() } [Fact] - public static void EnumSchema_UnknownNestedArrays_AreIgnored() + public static void UntitledSingleSelectEnumSchema_UnknownProperties_AreIgnored() { - // Test complex unknown properties with arrays of objects - - const string jsonWithNestedArrays = """ + const string json = """ { "type": "string", "enum": ["option1", "option2", "option3"], - "enumNames": ["Name1", "Name2", "Name3"], - "unknownComplex": [ - { - "nested": [ - {"deep": "value1"}, - {"deep": "value2"} - ] - }, - { - "nested": [ - {"deep": "value3"} - ] - } - ], + + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + "default": "option1" } """; var result = JsonSerializer.Deserialize( - jsonWithNestedArrays, + json, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); - var enumSchema = Assert.IsType(result); + var enumSchema = Assert.IsType(result); Assert.Equal("string", enumSchema.Type); Assert.Equal(3, enumSchema.Enum.Count); Assert.Contains("option1", enumSchema.Enum); Assert.Contains("option2", enumSchema.Enum); Assert.Contains("option3", enumSchema.Enum); - Assert.Equal(3, enumSchema.EnumNames!.Count); - Assert.Contains("Name1", enumSchema.EnumNames); - Assert.Contains("Name2", enumSchema.EnumNames); - Assert.Contains("Name3", enumSchema.EnumNames); Assert.Equal("option1", enumSchema.Default); } [Fact] - public static void StringSchema_MultipleUnknownProperties_AllIgnored() + public static void TitledSingleSelectEnumSchema_UnknownProperties_AreIgnored() { - // Test that multiple unknown properties are all properly skipped - - const string jsonWithMultipleUnknown = """ + const string json = """ { "type": "string", - "title": "Test", - "unknownOne": {"a": 1}, - "minLength": 5, - "unknownTwo": [1, 2, 3], - "maxLength": 50, - "unknownThree": {"b": {"c": "d"}}, - "format": "email" - } - """; - - var result = JsonSerializer.Deserialize( - jsonWithMultipleUnknown, - McpJsonUtilities.DefaultOptions); - - Assert.NotNull(result); - var stringSchema = Assert.IsType(result); - Assert.Equal("string", stringSchema.Type); - Assert.Equal("Test", stringSchema.Title); - Assert.Equal(5, stringSchema.MinLength); - Assert.Equal(50, stringSchema.MaxLength); - Assert.Equal("email", stringSchema.Format); - } + "oneOf": [ + {"const": "option1", "title": "Option 1"}, + {"const": "option2", "title": "Option 2"} + ], - [Fact] - public static void IntegerSchema_UnknownArrayOfArrays_IsIgnored() - { - // Test deeply nested array structures in unknown properties + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, - const string jsonWithArrayOfArrays = """ - { - "type": "integer", - "minimum": 1, - "maximum": 100, - "unknownNested": [ - [ - [1, 2, 3], - [4, 5, 6] - ], - [ - [7, 8, 9] - ] - ] + "default": "option2" } """; var result = JsonSerializer.Deserialize( - jsonWithArrayOfArrays, + json, McpJsonUtilities.DefaultOptions); - Assert.NotNull(result); - var numberSchema = Assert.IsType(result); - Assert.Equal("integer", numberSchema.Type); - Assert.Equal(1, numberSchema.Minimum); - Assert.Equal(100, numberSchema.Maximum); + var enumSchema = Assert.IsType(result); + Assert.Equal("string", enumSchema.Type); + Assert.Equal(2, enumSchema.OneOf.Count); + Assert.Contains(enumSchema.OneOf, option => option.Const == "option1" && option.Title == "Option 1"); + Assert.Contains(enumSchema.OneOf, option => option.Const == "option2" && option.Title == "Option 2"); + Assert.Equal("option2", enumSchema.Default); } [Fact] - public static void StringSchema_EmptyUnknownArray_IsIgnored() + public static void UntitledMultiSelectEnumSchema_UnknownProperties_AreIgnored() { - // Test empty arrays in unknown properties - - const string jsonWithEmptyArray = """ + const string json = """ { - "type": "string", - "description": "Test", - "unknownEmpty": [], - "minLength": 0 - } - """; - - var result = JsonSerializer.Deserialize( - jsonWithEmptyArray, - McpJsonUtilities.DefaultOptions); - - Assert.NotNull(result); - var stringSchema = Assert.IsType(result); - Assert.Equal("string", stringSchema.Type); - Assert.Equal("Test", stringSchema.Description); - Assert.Equal(0, stringSchema.MinLength); - } + "type": "array", + "items": { + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + + "enum": ["optionA", "optionB", "optionC"] + }, - [Fact] - public static void NumberSchema_EmptyUnknownObject_IsIgnored() - { - // Test empty objects in unknown properties + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, - const string jsonWithEmptyObject = """ - { - "type": "number", - "title": "Test Number", - "unknownEmpty": {}, - "minimum": 0.0, - "maximum": 100.0 + "default": ["optionA", "optionC"] } """; var result = JsonSerializer.Deserialize( - jsonWithEmptyObject, + json, McpJsonUtilities.DefaultOptions); - Assert.NotNull(result); - var numberSchema = Assert.IsType(result); - Assert.Equal("number", numberSchema.Type); - Assert.Equal("Test Number", numberSchema.Title); - Assert.Equal(0.0, numberSchema.Minimum); - Assert.Equal(100.0, numberSchema.Maximum); + var enumSchema = Assert.IsType(result); + Assert.Equal("array", enumSchema.Type); + Assert.Equal(3, enumSchema.Items.Enum.Count); + Assert.Contains("optionA", enumSchema.Items.Enum); + Assert.Contains("optionB", enumSchema.Items.Enum); + Assert.Contains("optionC", enumSchema.Items.Enum); + Assert.Equal(2, enumSchema.Default!.Count); + Assert.Contains("optionA", enumSchema.Default); + Assert.Contains("optionC", enumSchema.Default); } [Fact] - public static void EnumSchema_UnknownPropertiesBetweenRequired_AreIgnored() + public static void TitledMultiSelectEnumSchema_UnknownProperties_AreIgnored() { - // Test unknown properties interspersed with required ones - - const string jsonWithInterspersedUnknown = """ + const string json = """ { - "unknownFirst": {"x": 1}, - "type": "string", - "unknownSecond": [1, 2], - "enum": ["a", "b"], - "unknownThird": {"nested": {"value": true}}, - "enumNames": ["Alpha", "Beta"] - } - """; - - var result = JsonSerializer.Deserialize( - jsonWithInterspersedUnknown, - McpJsonUtilities.DefaultOptions); - - Assert.NotNull(result); - var enumSchema = Assert.IsType(result); - Assert.Equal("string", enumSchema.Type); - Assert.Equal(2, enumSchema.Enum.Count); - Assert.Contains("a", enumSchema.Enum); - Assert.Contains("b", enumSchema.Enum); - Assert.Equal(2, enumSchema.EnumNames!.Count); - Assert.Contains("Alpha", enumSchema.EnumNames); - Assert.Contains("Beta", enumSchema.EnumNames); - } + "type": "array", + "items": { + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + + "anyOf": [ + {"const": "optionX", "title": "Option X"}, + {"const": "optionY", "title": "Option Y"}, + {"const": "optionZ", "title": "Option Z"} + ] + }, - [Fact] - public static void BooleanSchema_VeryDeeplyNestedUnknown_IsIgnored() - { - // Test very deeply nested structures in unknown properties + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, - const string jsonWithVeryDeepNesting = """ - { - "type": "boolean", - "unknownDeep": { - "level1": { - "level2": { - "level3": { - "level4": { - "level5": { - "value": "deep" - } - } - } - } - } - }, - "default": true + "default": ["optionX", "optionZ"] } """; var result = JsonSerializer.Deserialize( - jsonWithVeryDeepNesting, + json, McpJsonUtilities.DefaultOptions); - Assert.NotNull(result); - var boolSchema = Assert.IsType(result); - Assert.Equal("boolean", boolSchema.Type); - Assert.True(boolSchema.Default); + var enumSchema = Assert.IsType(result); + Assert.Equal("array", enumSchema.Type); + Assert.Equal(3, enumSchema.Items.AnyOf.Count); + Assert.Contains(enumSchema.Items.AnyOf, option => option.Const == "optionX" && option.Title == "Option X"); + Assert.Contains(enumSchema.Items.AnyOf, option => option.Const == "optionY" && option.Title == "Option Y"); + Assert.Contains(enumSchema.Items.AnyOf, option => option.Const == "optionZ" && option.Title == "Option Z"); + Assert.Equal(2, enumSchema.Default!.Count); + Assert.Contains("optionX", enumSchema.Default); + Assert.Contains("optionZ", enumSchema.Default); } +#pragma warning disable MCP9001 // LegacyTitledEnumSchema is deprecated but supported for backward compatibility [Fact] - public static void EnumSchema_Deserialization_PreservesKnownProperties() + public static void LegacyTitledEnumSchema_UnknownProperties_AreIgnored() { - // Test deserialization of enum schema with all properties - - const string enumSchemaJson = """ + const string json = """ { "type": "string", - "title": "Test Enum", - "description": "A test enum schema", - "enum": ["option1", "option2", "option3"], - "enumNames": ["Name1", "Name2", "Name3"], + "enum": ["option1", "option2"], + + "unknownNull": null, + "unknownEmptyObject": {}, + "unknownObject": {"a": 1}, + "unknownEmptyArray": [], + "unknownArray": [1, 2, 3], + "unknownNestedObject": {"b": {"c": "d", "e": ["f"]}}, + + "enumNames": ["Option 1", "Option 2"], "default": "option2" } """; - var deserialized = JsonSerializer.Deserialize( - enumSchemaJson, + var result = JsonSerializer.Deserialize( + json, McpJsonUtilities.DefaultOptions); - - Assert.NotNull(deserialized); - var enumSchema = Assert.IsType(deserialized); + Assert.NotNull(result); + var enumSchema = Assert.IsType(result); Assert.Equal("string", enumSchema.Type); - Assert.Equal("Test Enum", enumSchema.Title); - Assert.Equal("A test enum schema", enumSchema.Description); - Assert.Equal(3, enumSchema.Enum.Count); + Assert.Equal(2, enumSchema.Enum.Count); Assert.Contains("option1", enumSchema.Enum); Assert.Contains("option2", enumSchema.Enum); - Assert.Contains("option3", enumSchema.Enum); - Assert.Equal(3, enumSchema.EnumNames!.Count); - Assert.Contains("Name1", enumSchema.EnumNames); - Assert.Contains("Name2", enumSchema.EnumNames); - Assert.Contains("Name3", enumSchema.EnumNames); - Assert.Equal("option2", enumSchema.Default); + Assert.Contains("Option 1", enumSchema.EnumNames!); + Assert.Contains("Option 2", enumSchema.EnumNames!); } +#pragma warning restore MCP9001 } diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs deleted file mode 100644 index 1ba3a5145..000000000 --- a/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Microsoft.Extensions.AI; -using ModelContextProtocol.Protocol; -using ModelContextProtocol.Server; -using Moq; -using System.Text.Json; - -namespace ModelContextProtocol.Tests.Server; - -#pragma warning disable CS0618 // Type or member is obsolete - -public class McpServerExtensionsTests -{ - [Fact] - public async Task SampleAsync_Request_Throws_When_Not_McpServer() - { - var server = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await server.SampleAsync( - new CreateMessageRequestParams - { - Messages = [new SamplingMessage { Role = Role.User, Content = [new TextContentBlock { Text = "hi" }] }], - MaxTokens = 1000 - }, - TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.SampleAsync' instead", ex.Message); - } - - [Fact] - public async Task SampleAsync_Messages_Throws_When_Not_McpServer() - { - var server = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await server.SampleAsync( - [new ChatMessage(ChatRole.User, "hi")], cancellationToken: TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.SampleAsync' instead", ex.Message); - } - - [Fact] - public void AsSamplingChatClient_Throws_When_Not_McpServer() - { - var server = new Mock(MockBehavior.Strict).Object; - - var ex = Assert.Throws(server.AsSamplingChatClient); - Assert.Contains("Prefer using 'McpServer.AsSamplingChatClient' instead", ex.Message); - } - - [Fact] - public void AsClientLoggerProvider_Throws_When_Not_McpServer() - { - var server = new Mock(MockBehavior.Strict).Object; - - var ex = Assert.Throws(server.AsClientLoggerProvider); - Assert.Contains("Prefer using 'McpServer.AsClientLoggerProvider' instead", ex.Message); - } - - [Fact] - public async Task RequestRootsAsync_Throws_When_Not_McpServer() - { - var server = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await server.RequestRootsAsync( - new ListRootsRequestParams(), TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.RequestRootsAsync' instead", ex.Message); - } - - [Fact] - public async Task ElicitAsync_Throws_When_Not_McpServer() - { - var server = new Mock(MockBehavior.Strict).Object; - - var ex = await Assert.ThrowsAsync(async () => await server.ElicitAsync( - new ElicitRequestParams { Message = "hello" }, TestContext.Current.CancellationToken)); - Assert.Contains("Prefer using 'McpServer.ElicitAsync' instead", ex.Message); - } - - [Fact] - public async Task SampleAsync_Request_Forwards_To_McpServer_SendRequestAsync() - { - var mockServer = new Mock { CallBase = true }; - - var resultPayload = new CreateMessageResult - { - Content = [new TextContentBlock { Text = "resp" }], - Model = "test-model", - Role = Role.Assistant, - StopReason = "endTurn", - }; - - mockServer - .Setup(s => s.ClientCapabilities) - .Returns(new ClientCapabilities() { Sampling = new() }); - - mockServer - .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpServer server = mockServer.Object; - - var result = await server.SampleAsync(new CreateMessageRequestParams - { - Messages = [new SamplingMessage { Role = Role.User, Content = [new TextContentBlock { Text = "hi" }] }], - MaxTokens = 1000 - }, TestContext.Current.CancellationToken); - - Assert.Equal("test-model", result.Model); - Assert.Equal(Role.Assistant, result.Role); - Assert.Equal("resp", Assert.IsType(result.Content[0]).Text); - mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task SampleAsync_Messages_Forwards_To_McpServer_SendRequestAsync() - { - var mockServer = new Mock { CallBase = true }; - - var resultPayload = new CreateMessageResult - { - Content = [new TextContentBlock { Text = "resp" }], - Model = "test-model", - Role = Role.Assistant, - StopReason = "endTurn", - }; - - const int CustomMaxSamplingOutputTokens = 500; - - mockServer - .Setup(s => s.ClientCapabilities) - .Returns(new ClientCapabilities() { Sampling = new() }); - - mockServer - .Setup(s => s.ServerOptions) - .Returns(new McpServerOptions { MaxSamplingOutputTokens = CustomMaxSamplingOutputTokens }); - - CreateMessageRequestParams? capturedRequest = null; - mockServer - .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny())) - .Callback((request, _) => - { - capturedRequest = JsonSerializer.Deserialize( - request.Params ?? throw new InvalidOperationException(), - McpJsonUtilities.DefaultOptions); - }) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpServer server = mockServer.Object; - - var chatResponse = await server.SampleAsync([new ChatMessage(ChatRole.User, "hi")], cancellationToken: TestContext.Current.CancellationToken); - - Assert.Equal("test-model", chatResponse.ModelId); - var last = chatResponse.Messages.Last(); - Assert.Equal(ChatRole.Assistant, last.Role); - Assert.Equal("resp", last.Text); - mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - - // Verify that the default value was used - Assert.NotNull(capturedRequest); - Assert.Equal(CustomMaxSamplingOutputTokens, capturedRequest.MaxTokens); - } - - [Fact] - public async Task RequestRootsAsync_Forwards_To_McpServer_SendRequestAsync() - { - var mockServer = new Mock { CallBase = true }; - - var resultPayload = new ListRootsResult { Roots = [new Root { Uri = "root://a" }] }; - - mockServer - .Setup(s => s.ClientCapabilities) - .Returns(new ClientCapabilities() { Roots = new() }); - - mockServer - .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpServer server = mockServer.Object; - - var result = await server.RequestRootsAsync(new ListRootsRequestParams(), TestContext.Current.CancellationToken); - - Assert.Equal("root://a", result.Roots[0].Uri); - mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public async Task ElicitAsync_Forwards_To_McpServer_SendRequestAsync() - { - var mockServer = new Mock { CallBase = true }; - - var resultPayload = new ElicitResult { Action = "accept" }; - - mockServer - .Setup(s => s.ClientCapabilities) - .Returns(new ClientCapabilities() { Elicitation = new() }); - - mockServer - .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new JsonRpcResponse - { - Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions), - }); - - IMcpServer server = mockServer.Object; - - var result = await server.ElicitAsync(new ElicitRequestParams { Message = "hi" }, TestContext.Current.CancellationToken); - - Assert.Equal("accept", result.Action); - mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); - } -}