diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md index 43574929b..ae4cab4fb 100644 --- a/docs/list-of-diagnostics.md +++ b/docs/list-of-diagnostics.md @@ -24,6 +24,7 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T | Diagnostic ID | Description | | :------------ | :---------- | | `MCPEXP001` | MCP experimental APIs including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). | +| `MCPEXP002` | Subclassing `McpClient` and `McpServer` is experimental and subject to change (see [#1363](https://github.com/modelcontextprotocol/csharp-sdk/pull/1363)). | ## Obsolete APIs @@ -34,4 +35,4 @@ When APIs are marked as obsolete, a diagnostic is emitted to warn users that the | Diagnostic ID | Status | Description | | :------------ | :----- | :---------- | | `MCP9001` | In place | The `EnumSchema` and `LegacyTitledEnumSchema` APIs are deprecated as of specification version 2025-11-25. Use the current schema APIs instead. | -| `MCP9002` | Removed | The `AddXxxFilter` extension methods on `IMcpServerBuilder` (e.g., `AddListToolsFilter`, `AddCallToolFilter`, `AddIncomingMessageFilter`) were superseded by `WithRequestFilters()` and `WithMessageFilters()`. | \ No newline at end of file +| `MCP9002` | Removed | The `AddXxxFilter` extension methods on `IMcpServerBuilder` (e.g., `AddListToolsFilter`, `AddCallToolFilter`, `AddIncomingMessageFilter`) were superseded by `WithRequestFilters()` and `WithMessageFilters()`. | diff --git a/src/Common/Experimentals.cs b/src/Common/Experimentals.cs index ec2c7c550..dd37a0766 100644 --- a/src/Common/Experimentals.cs +++ b/src/Common/Experimentals.cs @@ -56,4 +56,19 @@ internal static class Experimentals /// URL for the experimental MCP Extensions feature. /// public const string Extensions_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp001"; + + /// + /// Diagnostic ID for experimental subclassing of McpClient and McpServer. + /// + public const string Subclassing_DiagnosticId = "MCPEXP002"; + + /// + /// Message for experimental subclassing of McpClient and McpServer. + /// + public const string Subclassing_Message = "Subclassing McpClient and McpServer is experimental and subject to change."; + + /// + /// URL for experimental subclassing of McpClient and McpServer. + /// + public const string Subclassing_Url = "https://github.com/modelcontextprotocol/csharp-sdk/pull/1363"; } diff --git a/src/ModelContextProtocol.Core/Client/McpClient.cs b/src/ModelContextProtocol.Core/Client/McpClient.cs index 28a37f258..acd0bb12d 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.cs @@ -1,4 +1,5 @@ -using ModelContextProtocol.Protocol; +using System.Diagnostics.CodeAnalysis; +using ModelContextProtocol.Protocol; namespace ModelContextProtocol.Client; @@ -7,6 +8,14 @@ namespace ModelContextProtocol.Client; /// public abstract partial class McpClient : McpSession { + /// + /// Initializes a new instance of the class. + /// + [Experimental(Experimentals.Subclassing_DiagnosticId, UrlFormat = Experimentals.Subclassing_Url)] + protected McpClient() + { + } + /// /// Gets the capabilities supported by the connected server. /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index 9375e2760..8317656d0 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -6,6 +6,7 @@ namespace ModelContextProtocol.Client; /// +#pragma warning disable MCPEXP002 internal sealed partial class McpClientImpl : McpClient { private static Implementation DefaultImplementation { get; } = new() @@ -37,6 +38,7 @@ internal sealed partial class McpClientImpl : McpClient /// Options for the client, defining protocol version and capabilities. /// The logger factory. internal McpClientImpl(ITransport transport, string endpointName, McpClientOptions? options, ILoggerFactory? loggerFactory) +#pragma warning restore MCPEXP002 { options ??= new(); diff --git a/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs b/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs index 784e0f9a6..957f58a51 100644 --- a/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs +++ b/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs @@ -3,7 +3,9 @@ namespace ModelContextProtocol.Server; +#pragma warning disable MCPEXP002 internal sealed class DestinationBoundMcpServer(McpServerImpl server, ITransport? transport) : McpServer +#pragma warning restore MCPEXP002 { public override string? SessionId => transport?.SessionId ?? server.SessionId; public override string? NegotiatedProtocolVersion => server.NegotiatedProtocolVersion; diff --git a/src/ModelContextProtocol.Core/Server/McpServer.cs b/src/ModelContextProtocol.Core/Server/McpServer.cs index 2d8ea6826..b8b41bdc3 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using ModelContextProtocol.Protocol; namespace ModelContextProtocol.Server; @@ -7,6 +8,14 @@ namespace ModelContextProtocol.Server; /// public abstract partial class McpServer : McpSession { + /// + /// Initializes a new instance of the class. + /// + [Experimental(Experimentals.Subclassing_DiagnosticId, UrlFormat = Experimentals.Subclassing_Url)] + protected McpServer() + { + } + /// /// Gets the capabilities supported by the client. /// diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 52e538e7e..39feae5d6 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -9,6 +9,7 @@ namespace ModelContextProtocol.Server; /// +#pragma warning disable MCPEXP002 internal sealed partial class McpServerImpl : McpServer { internal static Implementation DefaultImplementation { get; } = new() @@ -54,6 +55,7 @@ internal sealed partial class McpServerImpl : McpServer /// Optional service provider to use for dependency injection /// The server was incorrectly configured. public McpServerImpl(ITransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider) +#pragma warning restore MCPEXP002 { Throw.IfNull(transport); Throw.IfNull(options); diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index 046f08e8b..53fd50685 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -943,7 +943,9 @@ private static async Task InitializeServerAsync(TestServerTransport transport, C await tcs.Task.WaitAsync(TestConstants.DefaultTimeout, cancellationToken); } +#pragma warning disable MCPEXP002 private sealed class TestServerForIChatClient(bool supportsSampling) : McpServer +#pragma warning restore MCPEXP002 { public override ClientCapabilities? ClientCapabilities => supportsSampling ? new ClientCapabilities { Sampling = new SamplingCapability() } :