From 6cc05aa6e1ef25a42e133fc6646766b6eb790616 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:04:24 +0000 Subject: [PATCH 01/15] Make RequestContext.Params non-nullable with new constructor; obsolete old constructor - Add new RequestContext(McpServer, JsonRpcRequest, TParams) constructor - Obsolete the old parameterless constructor - Change Params property from TParams? to TParams - Update all production code to use non-nullable Params - Update RequestHandlers.Set to use non-null TParams - Update InvokeHandlerAsync to use new constructor Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/a857ef9f-506e-4046-a2c2-d268cfc65701 --- .../RequestHandlers.cs | 4 +- .../Server/AIFunctionMcpServerPrompt.cs | 2 +- .../Server/AIFunctionMcpServerResource.cs | 12 +-- .../Server/AIFunctionMcpServerTool.cs | 2 +- .../Server/McpServerImpl.cs | 80 +++++++++---------- .../Server/RequestContext.cs | 17 +++- .../Server/RequestServiceProvider.cs | 2 +- 7 files changed, 65 insertions(+), 54 deletions(-) diff --git a/src/ModelContextProtocol.Core/RequestHandlers.cs b/src/ModelContextProtocol.Core/RequestHandlers.cs index fd95751d9..97e8b95df 100644 --- a/src/ModelContextProtocol.Core/RequestHandlers.cs +++ b/src/ModelContextProtocol.Core/RequestHandlers.cs @@ -29,7 +29,7 @@ internal sealed class RequestHandlers : Dictionary public void Set( string method, - Func> handler, + Func> handler, JsonTypeInfo requestTypeInfo, JsonTypeInfo responseTypeInfo) { @@ -40,7 +40,7 @@ public void Set( this[method] = async (request, cancellationToken) => { - TParams? typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo); + TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!; object? result = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false); return JsonSerializer.SerializeToNode(result, responseTypeInfo); }; diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs index c755cd9e5..30f31c308 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs @@ -201,7 +201,7 @@ public override async ValueTask GetAsync( request.Services = new RequestServiceProvider(request); AIFunctionArguments arguments = new() { Services = request.Services }; - if (request.Params?.Arguments is { } argDict) + if (request.Params.Arguments is { } argDict) { foreach (var kvp in argDict) { diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs index 3413d7038..7f0f29140 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs @@ -386,14 +386,14 @@ public override async ValueTask ReadAsync( TextContent tc => new() { - Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }], + Contents = [new TextResourceContents { Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }], }, DataContent dc => new() { Contents = [new BlobResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = dc.MediaType, Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span) }], @@ -401,7 +401,7 @@ public override async ValueTask ReadAsync( string text => new() { - Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }], + Contents = [new TextResourceContents { Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }], }, IEnumerable contents => new() @@ -416,14 +416,14 @@ public override async ValueTask ReadAsync( { TextContent tc => new TextResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }, DataContent dc => new BlobResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = dc.MediaType, Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span) }, @@ -436,7 +436,7 @@ public override async ValueTask ReadAsync( { Contents = strings.Select(text => new TextResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }).ToList(), diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs index e91bdd206..cf7d9925f 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs @@ -255,7 +255,7 @@ public override async ValueTask InvokeAsync( request.Services = new RequestServiceProvider(request); AIFunctionArguments arguments = new() { Services = request.Services }; - if (request.Params?.Arguments is { } argDict) + if (request.Params.Arguments is { } argDict) { foreach (var kvp in argDict) { diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 753d91667..06fa965e1 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -208,8 +208,8 @@ private void ConfigureInitialize(McpServerOptions options) _requestHandlers.Set(RequestMethods.Initialize, async (request, _, _) => { - _clientCapabilities = request?.Capabilities ?? new(); - _clientInfo = request?.ClientInfo; + _clientCapabilities = request.Capabilities ?? new(); + _clientInfo = request.ClientInfo; // Use the ClientInfo to update the session EndpointName for logging. UpdateEndpointNameWithClientInfo(); @@ -219,7 +219,7 @@ private void ConfigureInitialize(McpServerOptions options) // Otherwise, try to use whatever the client requested as long as it's supported. // If it's not supported, fall back to the latest supported version. string? protocolVersion = options.ProtocolVersion; - protocolVersion ??= request?.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ? + protocolVersion ??= request.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ? clientProtocolVersion : McpSessionHandler.LatestProtocolVersion; @@ -266,7 +266,7 @@ private void ConfigureCompletion(McpServerOptions options) CompleteResult result = await originalCompleteHandler(request, cancellationToken).ConfigureAwait(false); string[]? allowedValues = null; - switch (request.Params?.Ref) + switch (request.Params.Ref) { case PromptReference pr when promptCompletions is not null: if (promptCompletions.TryGetValue(pr.Name, out var promptParams)) @@ -285,7 +285,7 @@ private void ConfigureCompletion(McpServerOptions options) if (allowedValues is not null) { - string partialValue = request.Params!.Argument.Value; + string partialValue = request.Params.Argument.Value; foreach (var v in allowedValues) { if (v.StartsWith(partialValue, StringComparison.OrdinalIgnoreCase)) @@ -409,7 +409,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null && listResourcesHandler ??= (static async (_, __) => new ListResourcesResult()); listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult()); - readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.ResourceNotFound)); + readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound)); subscribeHandler ??= (static async (_, __) => new EmptyResult()); unsubscribeHandler ??= (static async (_, __) => new EmptyResult()); var listChanged = resourcesCapability?.ListChanged; @@ -425,7 +425,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null && await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params?.Cursor is null) + if (request.Params.Cursor is null) { foreach (var r in resources) { @@ -446,7 +446,7 @@ await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(fa await originalListResourceTemplatesHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params?.Cursor is null) + if (request.Params.Cursor is null) { foreach (var rt in resources) { @@ -484,7 +484,7 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure async (request, cancellationToken) => { // Initial handler that sets MatchedPrimitive - if (request.Params?.Uri is { } uri && resources is not null) + if (request.Params.Uri is { } uri && resources is not null) { // First try an O(1) lookup by exact match. if (resources.TryGetPrimitive(uri, out var resource) && !resource.IsTemplated) @@ -508,12 +508,12 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure try { var result = await handler(request, cancellationToken).ConfigureAwait(false); - ReadResourceCompleted(request.Params?.Uri ?? string.Empty); + ReadResourceCompleted(request.Params.Uri ?? string.Empty); return result; } catch (Exception e) { - ReadResourceError(request.Params?.Uri ?? string.Empty, e); + ReadResourceError(request.Params.Uri ?? string.Empty, e); throw; } }); @@ -570,7 +570,7 @@ private void ConfigurePrompts(McpServerOptions options) ServerCapabilities.Prompts = new(); listPromptsHandler ??= (static async (_, __) => new ListPromptsResult()); - getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams)); + getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params.Name}'", McpErrorCode.InvalidParams)); var listChanged = promptsCapability?.ListChanged; // Handle tools provided via DI by augmenting the handlers to incorporate them. @@ -583,7 +583,7 @@ private void ConfigurePrompts(McpServerOptions options) await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params?.Cursor is null) + if (request.Params.Cursor is null) { foreach (var p in prompts) { @@ -613,7 +613,7 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals async (request, cancellationToken) => { // Initial handler that sets MatchedPrimitive - if (request.Params?.Name is { } promptName && prompts is not null && + if (request.Params.Name is { } promptName && prompts is not null && prompts.TryGetPrimitive(promptName, out var prompt)) { request.MatchedPrimitive = prompt; @@ -622,12 +622,12 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals try { var result = await handler(request, cancellationToken).ConfigureAwait(false); - GetPromptCompleted(request.Params?.Name ?? string.Empty); + GetPromptCompleted(request.Params.Name ?? string.Empty); return result; } catch (Exception e) { - GetPromptError(request.Params?.Name ?? string.Empty, e); + GetPromptError(request.Params.Name ?? string.Empty, e); throw; } }); @@ -663,7 +663,7 @@ private void ConfigureTools(McpServerOptions options) ServerCapabilities.Tools = new(); listToolsHandler ??= (static async (_, __) => new ListToolsResult()); - callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams)); + callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams)); var listChanged = toolsCapability?.ListChanged; // Handle tools provided via DI by augmenting the handlers to incorporate them. @@ -676,7 +676,7 @@ private void ConfigureTools(McpServerOptions options) await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params?.Cursor is null) + if (request.Params.Cursor is null) { foreach (var t in tools) { @@ -697,7 +697,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) var taskSupport = tool.ProtocolTool.Execution?.TaskSupport ?? ToolTaskSupport.Forbidden; // Check if this is a task-augmented request - if (request.Params?.Task is { } taskMetadata) + if (request.Params.Task is { } taskMetadata) { // Validate tool-level task support if (taskSupport is ToolTaskSupport.Forbidden) @@ -735,7 +735,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) async (request, cancellationToken) => { // Initial handler that sets MatchedPrimitive - if (request.Params?.Name is { } toolName && tools is not null && + if (request.Params.Name is { } toolName && tools is not null && tools.TryGetPrimitive(toolName, out var tool)) { request.MatchedPrimitive = tool; @@ -749,14 +749,14 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) // in ExecuteToolAsTaskAsync when the tool actually completes. if (result.Task is null) { - ToolCallCompleted(request.Params?.Name ?? string.Empty, result.IsError is true); + ToolCallCompleted(request.Params.Name ?? string.Empty, result.IsError is true); } return result; } catch (Exception e) { - ToolCallError(request.Params?.Name ?? string.Empty, e); + ToolCallError(request.Params.Name ?? string.Empty, e); if ((e is OperationCanceledException && cancellationToken.IsCancellationRequested) || e is McpProtocolException) { @@ -769,8 +769,8 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) Content = [new TextContentBlock { Text = e is McpException ? - $"An error occurred invoking '{request.Params?.Name}': {e.Message}" : - $"An error occurred invoking '{request.Params?.Name}'.", + $"An error occurred invoking '{request.Params.Name}': {e.Message}" : + $"An error occurred invoking '{request.Params.Name}'.", }], }; } @@ -818,7 +818,7 @@ private void ConfigureTasks(McpServerOptions options) // tasks/get handler - Retrieve task status McpRequestHandler getTaskHandler = async (request, cancellationToken) => { - if (request.Params?.TaskId is not { } taskId) + if (request.Params.TaskId is not { } taskId) { throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams); } @@ -839,7 +839,7 @@ private void ConfigureTasks(McpServerOptions options) async Task GetTaskResultAsync(RequestContext request, CancellationToken cancellationToken) { - if (request.Params?.TaskId is not { } taskId) + if (request.Params.TaskId is not { } taskId) { throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams); } @@ -872,14 +872,14 @@ async Task GetTaskResultAsync(RequestContext listTasksHandler = async (request, cancellationToken) => { - var cursor = request.Params?.Cursor; + var cursor = request.Params.Cursor; return await taskStore.ListTasksAsync(cursor, SessionId, cancellationToken).ConfigureAwait(false); }; // tasks/cancel handler - Cancel a task McpRequestHandler cancelTaskHandler = async (request, cancellationToken) => { - if (request.Params?.TaskId is not { } taskId) + if (request.Params.TaskId is not { } taskId) { throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams); } @@ -941,16 +941,13 @@ private void ConfigureLogging(McpServerOptions options) (request, jsonRpcRequest, cancellationToken) => { // Store the provided level. - if (request is not null) + if (_loggingLevel is null) { - if (_loggingLevel is null) - { - Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null); - } - - _loggingLevel.Value = request.Level; + Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null); } + _loggingLevel.Value = request.Level; + // If a handler was provided, now delegate to it. if (setLoggingLevelHandler is not null) { @@ -966,17 +963,17 @@ private void ConfigureLogging(McpServerOptions options) private ValueTask InvokeHandlerAsync( McpRequestHandler handler, - TParams? args, + TParams args, JsonRpcRequest jsonRpcRequest, CancellationToken cancellationToken = default) { return _servicesScopePerRequest ? InvokeScopedAsync(handler, args, jsonRpcRequest, cancellationToken) : - handler(new(new DestinationBoundMcpServer(this, jsonRpcRequest.Context?.RelatedTransport), jsonRpcRequest) { Params = args }, cancellationToken); + handler(new(new DestinationBoundMcpServer(this, jsonRpcRequest.Context?.RelatedTransport), jsonRpcRequest, args), cancellationToken); async ValueTask InvokeScopedAsync( McpRequestHandler handler, - TParams? args, + TParams args, JsonRpcRequest jsonRpcRequest, CancellationToken cancellationToken) { @@ -984,10 +981,9 @@ async ValueTask InvokeScopedAsync( try { return await handler( - new RequestContext(new DestinationBoundMcpServer(this, jsonRpcRequest.Context?.RelatedTransport), jsonRpcRequest) + new RequestContext(new DestinationBoundMcpServer(this, jsonRpcRequest.Context?.RelatedTransport), jsonRpcRequest, args) { Services = scope?.ServiceProvider ?? Services, - Params = args }, cancellationToken).ConfigureAwait(false); } @@ -1167,7 +1163,7 @@ private async ValueTask ExecuteToolAsTaskAsync( // Invoke the tool with task-specific cancellation token var result = await tool.InvokeAsync(request, taskCancellationToken).ConfigureAwait(false); - ToolCallCompleted(request.Params?.Name ?? string.Empty, result.IsError is true); + ToolCallCompleted(request.Params.Name ?? string.Empty, result.IsError is true); // Determine final status based on whether there was an error var finalStatus = result.IsError is true ? McpTaskStatus.Failed : McpTaskStatus.Completed; @@ -1196,7 +1192,7 @@ private async ValueTask ExecuteToolAsTaskAsync( catch (Exception ex) { // Log the error - ToolCallError(request.Params?.Name ?? string.Empty, ex); + ToolCallError(request.Params.Name ?? string.Empty, ex); // Store error result var errorResult = new CallToolResult diff --git a/src/ModelContextProtocol.Core/Server/RequestContext.cs b/src/ModelContextProtocol.Core/Server/RequestContext.cs index 959bb07da..4a8168542 100644 --- a/src/ModelContextProtocol.Core/Server/RequestContext.cs +++ b/src/ModelContextProtocol.Core/Server/RequestContext.cs @@ -13,19 +13,34 @@ namespace ModelContextProtocol.Server; /// public sealed class RequestContext : MessageContext { + /// + /// Initializes a new instance of the class with the specified server, JSON-RPC request, and request parameters. + /// + /// The server with which this instance is associated. + /// The JSON-RPC request associated with this context. + /// The parameters associated with this request. + /// or is . + public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams @params) + : base(server, jsonRpcRequest) + { + Params = @params; + } + /// /// Initializes a new instance of the class with the specified server and JSON-RPC request. /// /// The server with which this instance is associated. /// The JSON-RPC request associated with this context. /// or is . + [Obsolete("Use the constructor that accepts a params argument.")] public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest) : base(server, jsonRpcRequest) { + Params = default!; } /// Gets or sets the parameters associated with this request. - public TParams? Params { get; set; } + public TParams Params { get; set; } /// /// Gets or sets the primitive that matched the request. diff --git a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs index 86f0a6884..0c3da62e2 100644 --- a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs +++ b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs @@ -27,7 +27,7 @@ public static bool IsAugmentedWith(Type serviceType) => serviceType == typeof(RequestContext) ? request : serviceType == typeof(McpServer) ? request.Server : serviceType == typeof(IProgress) ? - (request.Params?.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) : + (request.Params.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) : serviceType == typeof(ClaimsPrincipal) ? request.User : _innerServices?.GetService(serviceType); From f6960a254aca1ea3a43278719ecbd772b27798d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:35:45 +0000 Subject: [PATCH 02/15] Fix test files for RequestContext 3-arg constructor API change Update all test files to use the new 3-arg RequestContext constructor (server, jsonRpcRequest, params) instead of the old 2-arg constructor with property initializer for Params. Changes: - Move Params from object initializer to constructor argument - Change .Params?. to .Params. on RequestContext variables - Fix request?.Params?.X to request?.Params.X - Remove dead null checks on non-nullable value type Level - Add required Name/Uri properties to default params instances Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Program.cs | 11 +-- .../Tools/ConformanceTools.cs | 2 +- .../Program.cs | 49 ++++++------ .../Program.cs | 2 +- .../Client/McpClientMetaTests.cs | 6 +- .../Client/McpClientPromptTests.cs | 2 +- ...pClientResourceTemplateConstructorTests.cs | 2 +- .../Client/McpClientResourceTemplateTests.cs | 2 +- .../Client/McpClientResourceTests.cs | 2 +- .../Client/McpClientToolTests.cs | 2 +- ...rverBuilderExtensionsMessageFilterTests.cs | 2 +- .../McpServerBuilderExtensionsPromptsTests.cs | 19 ++--- ...cpServerBuilderExtensionsResourcesTests.cs | 19 ++--- .../McpServerBuilderExtensionsToolsTests.cs | 8 +- .../McpProtocolExceptionDataTests.cs | 2 +- .../Protocol/ElicitationTypedTests.cs | 14 ++-- .../Server/McpServerPromptTests.cs | 32 ++++---- .../Server/McpServerResourceTests.cs | 78 +++++++++---------- .../Server/McpServerToolTests.cs | 59 ++++++-------- 19 files changed, 141 insertions(+), 172 deletions(-) diff --git a/tests/ModelContextProtocol.ConformanceServer/Program.cs b/tests/ModelContextProtocol.ConformanceServer/Program.cs index 94c62727b..017ec235f 100644 --- a/tests/ModelContextProtocol.ConformanceServer/Program.cs +++ b/tests/ModelContextProtocol.ConformanceServer/Program.cs @@ -39,7 +39,7 @@ public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvide // For the test_reconnection tool, enable polling mode after the tool runs. // This stores the result and closes the SSE stream, so the client // must reconnect via GET with Last-Event-ID to retrieve the result. - if (request.Params?.Name == "test_reconnection") + if (request.Params.Name == "test_reconnection") { await request.EnablePollingAsync(TimeSpan.FromMilliseconds(500), cancellationToken); } @@ -54,7 +54,7 @@ public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvide { throw new McpException("Cannot add subscription for server with null SessionId"); } - if (ctx.Params?.Uri is { } uri) + if (ctx.Params.Uri is { } uri) { var sessionSubscriptions = subscriptions.GetOrAdd(ctx.Server.SessionId, _ => new()); sessionSubscriptions.TryAdd(uri, 0); @@ -68,7 +68,7 @@ public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvide { throw new McpException("Cannot remove subscription for server with null SessionId"); } - if (ctx.Params?.Uri is { } uri) + if (ctx.Params.Uri is { } uri) { subscriptions[ctx.Server.SessionId].TryRemove(uri, out _); } @@ -91,11 +91,6 @@ public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvide }) .WithSetLoggingLevelHandler(async (ctx, ct) => { - if (ctx.Params?.Level is null) - { - throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams); - } - // The SDK updates the LoggingLevel field of the McpServer // Send a log notification to confirm the level was set await ctx.Server.SendNotificationAsync("notifications/message", new LoggingMessageNotificationParams diff --git a/tests/ModelContextProtocol.ConformanceServer/Tools/ConformanceTools.cs b/tests/ModelContextProtocol.ConformanceServer/Tools/ConformanceTools.cs index 0a1f7ecfb..d6db6f626 100644 --- a/tests/ModelContextProtocol.ConformanceServer/Tools/ConformanceTools.cs +++ b/tests/ModelContextProtocol.ConformanceServer/Tools/ConformanceTools.cs @@ -132,7 +132,7 @@ public static async Task ToolWithProgress( RequestContext context, CancellationToken cancellationToken) { - var progressToken = context.Params?.ProgressToken; + var progressToken = context.Params.ProgressToken; if (progressToken is not null) { diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index 89ad9987d..9cb963a96 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -204,9 +204,9 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) }; options.Handlers.CallToolHandler = async (request, cancellationToken) => { - if (request.Params?.Name == "echo") + if (request.Params.Name == "echo") { - if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) + if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) { throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams); } @@ -215,16 +215,16 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) Content = [new TextContentBlock { Text = $"Echo: {message}" }] }; } - else if (request.Params?.Name == "echoSessionId") + else if (request.Params.Name == "echoSessionId") { return new CallToolResult { Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }] }; } - else if (request.Params?.Name == "trigger-sampling-request") + else if (request.Params.Name == "trigger-sampling-request") { - if (request.Params?.Arguments is null || + if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("prompt", out var prompt) || !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) { @@ -238,16 +238,16 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) Content = [new TextContentBlock { Text = $"LLM sampling result: {sampleResult.Content.OfType().FirstOrDefault()?.Text}" }] }; } - else if (request.Params?.Name == "echoCliArg") + else if (request.Params.Name == "echoCliArg") { return new CallToolResult { Content = [new TextContentBlock { Text = cliArg ?? "null" }] }; } - else if (request.Params?.Name == "longRunning") + else if (request.Params.Name == "longRunning") { - if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("durationMs", out var durationMsValue)) + if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("durationMs", out var durationMsValue)) { throw new McpProtocolException("Missing required argument 'durationMs'", McpErrorCode.InvalidParams); } @@ -258,9 +258,9 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) Content = [new TextContentBlock { Text = $"Long-running operation completed after {durationMs}ms" }] }; } - else if (request.Params?.Name == "crash") + else if (request.Params.Name == "crash") { - if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("exitCode", out var exitCodeValue)) + if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("exitCode", out var exitCodeValue)) { throw new McpProtocolException("Missing required argument 'exitCode'", McpErrorCode.InvalidParams); } @@ -271,7 +271,7 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) } else { - throw new McpProtocolException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams); + throw new McpProtocolException($"Unknown tool: {request.Params.Name}", McpErrorCode.InvalidParams); } }; } @@ -335,7 +335,7 @@ private static void ConfigurePrompts(McpServerOptions options) options.Handlers.GetPromptHandler = async (request, cancellationToken) => { List messages = []; - if (request.Params?.Name == "simple-prompt") + if (request.Params.Name == "simple-prompt") { messages.Add(new PromptMessage { @@ -343,7 +343,7 @@ private static void ConfigurePrompts(McpServerOptions options) Content = new TextContentBlock { Text = "This is a simple prompt without arguments." }, }); } - else if (request.Params?.Name == "args-prompt") + else if (request.Params.Name == "args-prompt") { string city = request.Params.Arguments?["city"].ToString() ?? "unknown"; string state = request.Params.Arguments?["state"].ToString() ?? ""; @@ -354,7 +354,7 @@ private static void ConfigurePrompts(McpServerOptions options) Content = new TextContentBlock { Text = $"What's weather in {location}?" }, }); } - else if (request.Params?.Name == "completable-prompt") + else if (request.Params.Name == "completable-prompt") { string department = request.Params.Arguments?["department"].ToString() ?? "unknown"; string name = request.Params.Arguments?["name"].ToString() ?? "unknown"; @@ -366,7 +366,7 @@ private static void ConfigurePrompts(McpServerOptions options) } else { - throw new McpProtocolException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams); + throw new McpProtocolException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams); } return new GetPromptResult @@ -382,11 +382,6 @@ private static void ConfigureLogging(McpServerOptions options) { options.Handlers.SetLoggingLevelHandler = async (request, cancellationToken) => { - if (request.Params?.Level is null) - { - throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams); - } - _minimumLoggingLevel = request.Params.Level; return new EmptyResult(); @@ -452,7 +447,7 @@ private static void ConfigureResources(McpServerOptions options) options.Handlers.ListResourcesHandler = async (request, cancellationToken) => { int startIndex = 0; - if (request.Params?.Cursor is not null) + if (request.Params.Cursor is not null) { try { @@ -481,7 +476,7 @@ private static void ConfigureResources(McpServerOptions options) options.Handlers.ReadResourceHandler = async (request, cancellationToken) => { - if (request.Params?.Uri is null) + if (request.Params.Uri is null) { throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams); } @@ -518,7 +513,7 @@ private static void ConfigureResources(McpServerOptions options) options.Handlers.SubscribeToResourcesHandler = async (request, cancellationToken) => { - if (request?.Params?.Uri is null) + if (request?.Params.Uri is null) { throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams); } @@ -535,7 +530,7 @@ private static void ConfigureResources(McpServerOptions options) options.Handlers.UnsubscribeFromResourcesHandler = async (request, cancellationToken) => { - if (request?.Params?.Uri is null) + if (request?.Params.Uri is null) { throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams); } @@ -563,7 +558,7 @@ private static void ConfigureCompletions(McpServerOptions options) options.Handlers.CompleteHandler = async (request, cancellationToken) => { string[]? values; - switch (request.Params?.Ref) + switch (request.Params.Ref) { case ResourceTemplateReference rtr: var resourceId = rtr.Uri?.Split('/').LastOrDefault(); @@ -571,7 +566,7 @@ private static void ConfigureCompletions(McpServerOptions options) return new CompleteResult { Completion = new() { Values = [] } }; // Filter resource IDs that start with the input value - values = sampleResourceIds.Where(id => id.StartsWith(request.Params!.Argument.Value)).ToArray(); + values = sampleResourceIds.Where(id => id.StartsWith(request.Params.Argument.Value)).ToArray(); return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } }; case PromptReference pr: @@ -583,7 +578,7 @@ private static void ConfigureCompletions(McpServerOptions options) return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } }; default: - throw new McpProtocolException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams); + throw new McpProtocolException($"Unknown reference type: '{request.Params.Ref.Type}'", McpErrorCode.InvalidParams); } }; } diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index 1a27c0c15..3c42e290a 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -280,7 +280,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st ReadResourceHandler = async (request, cancellationToken) => { - if (request.Params?.Uri is null) + if (request.Params.Uri is null) { throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams); } diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs index ddb30b720..863d8e671 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs @@ -37,7 +37,7 @@ public async Task ToolCallWithMetaFields() async (RequestContext context) => { // Access the foo property of _meta field from the request parameters - var metaFoo = context.Params?.Meta?["foo"]?.ToString(); + var metaFoo = context.Params.Meta?["foo"]?.ToString(); // Assert that the meta foo is correctly passed Assert.NotNull(metaFoo); @@ -73,7 +73,7 @@ public async Task ResourceReadWithMetaFields() (RequestContext context) => { // Access the foo property of _meta field from the request parameters - var metaFoo = context.Params?.Meta?["foo"]?.ToString(); + var metaFoo = context.Params.Meta?["foo"]?.ToString(); // Assert that the meta foo is correctly passed Assert.NotNull(metaFoo); @@ -109,7 +109,7 @@ public async Task PromptGetWithMetaFields() (RequestContext context) => { // Access the foo property of _meta field from the request parameters - var metaFoo = context.Params?.Meta?["foo"]?.ToString(); + var metaFoo = context.Params.Meta?["foo"]?.ToString(); // Assert that the meta foo is correctly passed Assert.NotNull(metaFoo); diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientPromptTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientPromptTests.cs index ec9f5f561..e84b98733 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientPromptTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientPromptTests.cs @@ -29,7 +29,7 @@ public static ChatMessage Greeting([Description("The name to greet")] string nam [McpServerPrompt, Description("Echoes back the metadata it receives")] public static ChatMessage MetadataEcho(RequestContext context) => - new(ChatRole.User, context.Params?.Meta?.ToJsonString() ?? "{}"); + new(ChatRole.User, context.Params.Meta?.ToJsonString() ?? "{}"); } [Fact] diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateConstructorTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateConstructorTests.cs index efaa647aa..6a415dd5e 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateConstructorTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateConstructorTests.cs @@ -27,7 +27,7 @@ private sealed class FileTemplateResources [McpServerResource, Description("Echoes back the metadata it receives")] public static string MetadataEcho(RequestContext context, [Description("An ID")] string id) => - context.Params?.Meta?.ToJsonString() ?? "{}"; + context.Params.Meta?.ToJsonString() ?? "{}"; } [Fact] diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs index 693dab282..9bafd8ea4 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs @@ -17,7 +17,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer mcpServerBuilder.WithReadResourceHandler((request, cancellationToken) => new ValueTask(new ReadResourceResult { - Contents = [new TextResourceContents { Text = request.Params?.Uri ?? "", Uri = request.Params?.Uri ?? "" }] + Contents = [new TextResourceContents { Text = request.Params.Uri ?? "", Uri = request.Params.Uri ?? "" }] })); } diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTests.cs index 52fe54b63..26e5f1d27 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTests.cs @@ -27,7 +27,7 @@ private sealed class SampleResources [McpServerResource, Description("Echoes back the metadata it receives")] public static string MetadataEcho(RequestContext context) => - context.Params?.Meta?.ToJsonString() ?? "{}"; + context.Params.Meta?.ToJsonString() ?? "{}"; } [Fact] diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs index e17fb6bc9..4ec64dae1 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs @@ -161,7 +161,7 @@ public static EmbeddedResourceBlock BinaryResourceTool() => [McpServerTool] public static TextContentBlock MetadataEchoTool(RequestContext context) { - var meta = context.Params?.Meta; + var meta = context.Params.Meta; var metaJson = meta?.ToJsonString() ?? "{}"; return new TextContentBlock { Text = metaJson }; } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsMessageFilterTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsMessageFilterTests.cs index ec396c5be..6883944b6 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsMessageFilterTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsMessageFilterTests.cs @@ -687,7 +687,7 @@ public static async Task ReportProgress( RequestContext context, CancellationToken cancellationToken) { - if (context.Params?.ProgressToken is { } token) + if (context.Params.ProgressToken is { } token) { await server.NotifyProgressAsync(token, new ProgressNotificationValue { diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs index 69405e16c..209469e7b 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs @@ -26,7 +26,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer mcpServerBuilder .WithListPromptsHandler(async (request, cancellationToken) => { - var cursor = request.Params?.Cursor; + var cursor = request.Params.Cursor; switch (cursor) { case null: @@ -68,7 +68,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer }) .WithGetPromptHandler(async (request, cancellationToken) => { - switch (request.Params?.Name) + switch (request.Params.Name) { case "FirstCustomPrompt": case "SecondCustomPrompt": @@ -79,7 +79,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer }; default: - throw new McpProtocolException($"Unknown prompt '{request.Params?.Name}'", McpErrorCode.InvalidParams); + throw new McpProtocolException($"Unknown prompt '{request.Params.Name}'", McpErrorCode.InvalidParams); } }) .WithPrompts(); @@ -314,17 +314,14 @@ public async Task WithPrompts_TargetInstance_UsesTarget() sc.AddMcpServer().WithPrompts(target); McpServerPrompt prompt = sc.BuildServiceProvider().GetServices().First(t => t.ProtocolPrompt.Name == "returns_string"); - var result = await prompt.GetAsync(new RequestContext(new Mock().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }) + var result = await prompt.GetAsync(new RequestContext(new Mock().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }, new GetPromptRequestParams { - Params = new GetPromptRequestParams + Name = "returns_string", + Arguments = new Dictionary { - Name = "returns_string", - Arguments = new Dictionary - { - ["message"] = JsonSerializer.SerializeToElement("hello", AIJsonUtilities.DefaultOptions), - } + ["message"] = JsonSerializer.SerializeToElement("hello", AIJsonUtilities.DefaultOptions), } - }, TestContext.Current.CancellationToken); + }), TestContext.Current.CancellationToken); Assert.Equal(target.ReturnsString("hello"), (result.Messages[0].Content as TextContentBlock)?.Text); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs index 545384a7c..4b03cadb2 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs @@ -25,7 +25,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer mcpServerBuilder .WithListResourcesHandler(async (request, cancellationToken) => { - var cursor = request.Params?.Cursor; + var cursor = request.Params.Cursor; switch (cursor) { case null: @@ -67,7 +67,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer }) .WithListResourceTemplatesHandler(async (request, cancellationToken) => { - var cursor = request.Params?.Cursor; + var cursor = request.Params.Cursor; switch (cursor) { case null: @@ -96,7 +96,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer }) .WithReadResourceHandler(async (request, cancellationToken) => { - switch (request.Params?.Uri) + switch (request.Params.Uri) { case "test://Resource1": case "test://Resource2": @@ -105,11 +105,11 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer case "test://ResourceTemplate2": return new ReadResourceResult { - Contents = [new TextResourceContents { Text = request.Params?.Uri ?? "(null)", Uri = request.Params?.Uri ?? "(null)" }] + Contents = [new TextResourceContents { Text = request.Params.Uri ?? "(null)", Uri = request.Params.Uri ?? "(null)" }] }; } - throw new McpProtocolException($"Resource not found: {request.Params?.Uri}", McpErrorCode.ResourceNotFound); + throw new McpProtocolException($"Resource not found: {request.Params.Uri}", McpErrorCode.ResourceNotFound); }) .WithResources(); } @@ -345,13 +345,10 @@ public async Task WithResources_TargetInstance_UsesTarget() sc.AddMcpServer().WithResources(target); McpServerResource resource = sc.BuildServiceProvider().GetServices().First(t => t.ProtocolResource?.Name == "returns_string"); - var result = await resource.ReadAsync(new RequestContext(new Mock().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }) + var result = await resource.ReadAsync(new RequestContext(new Mock().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }, new() { - Params = new() - { - Uri = "returns://string" - } - }, TestContext.Current.CancellationToken); + Uri = "returns://string" + }), TestContext.Current.CancellationToken); Assert.Equal(target.ReturnsString(), (result?.Contents[0] as TextResourceContents)?.Text); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index 518b70f00..d2db4c62c 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -30,7 +30,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer mcpServerBuilder .WithListToolsHandler(async (request, cancellationToken) => { - var cursor = request.Params?.Cursor; + var cursor = request.Params.Cursor; switch (cursor) { case null: @@ -93,7 +93,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer }) .WithCallToolHandler(async (request, cancellationToken) => { - switch (request.Params?.Name) + switch (request.Params.Name) { case "FirstCustomTool": case "SecondCustomTool": @@ -104,7 +104,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer }; default: - throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams); + throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams); } }) .WithTools(serializerOptions: BuilderToolsJsonContext.Default.Options); @@ -594,7 +594,7 @@ public async Task WithTools_TargetInstance_UsesTarget() sc.AddMcpServer().WithTools(target, BuilderToolsJsonContext.Default.Options); McpServerTool tool = sc.BuildServiceProvider().GetServices().First(t => t.ProtocolTool.Name == "get_ctor_parameter"); - var result = await tool.InvokeAsync(new RequestContext(new Mock().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }), TestContext.Current.CancellationToken); + var result = await tool.InvokeAsync(new RequestContext(new Mock().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }, new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal(target.GetCtorParameter(), (result.Content[0] as TextContentBlock)?.Text); } diff --git a/tests/ModelContextProtocol.Tests/McpProtocolExceptionDataTests.cs b/tests/ModelContextProtocol.Tests/McpProtocolExceptionDataTests.cs index 6816f5534..7d50a3044 100644 --- a/tests/ModelContextProtocol.Tests/McpProtocolExceptionDataTests.cs +++ b/tests/ModelContextProtocol.Tests/McpProtocolExceptionDataTests.cs @@ -28,7 +28,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer { mcpServerBuilder.WithCallToolHandler((request, cancellationToken) => { - var toolName = request.Params?.Name; + var toolName = request.Params.Name; switch (toolName) { diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs index 6f0988e48..7b55b738a 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs @@ -19,7 +19,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer { Assert.NotNull(request.Params); - if (request.Params!.Name == "TestElicitationTyped") + if (request.Params.Name == "TestElicitationTyped") { var result = await request.Server.ElicitAsync( message: "Please provide more information.", @@ -34,7 +34,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer Assert.Equal(SampleRole.Admin, result.Content!.Role); Assert.Equal(99.5, result.Content!.Score); } - else if (request.Params!.Name == "TestElicitationCamelForm") + else if (request.Params.Name == "TestElicitationCamelForm") { var result = await request.Server.ElicitAsync( message: "Please provide more information.", @@ -47,7 +47,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer Assert.Equal(90210, result.Content!.ZipCode); Assert.False(result.Content!.IsAdmin); } - else if (request.Params!.Name == "TestElicitationNullablePropertyForm") + else if (request.Params.Name == "TestElicitationNullablePropertyForm") { var result = await request.Server.ElicitAsync( message: "Please provide more information.", @@ -60,7 +60,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer Content = [new TextContentBlock { Text = "unexpected" }], }; } - else if (request.Params!.Name == "TestElicitationUnsupportedType") + else if (request.Params.Name == "TestElicitationUnsupportedType") { await request.Server.ElicitAsync( message: "Please provide more information.", @@ -73,7 +73,7 @@ await request.Server.ElicitAsync( Content = [new TextContentBlock { Text = "unexpected" }], }; } - else if (request.Params!.Name == "TestElicitationNonObjectGenericType") + else if (request.Params.Name == "TestElicitationNonObjectGenericType") { // This should throw because T is not an object type with properties (string primitive) await request.Server.ElicitAsync( @@ -86,7 +86,7 @@ await request.Server.ElicitAsync( Content = [new TextContentBlock { Text = "unexpected" }], }; } - else if (request.Params!.Name == "TestElicitationWithDefaults") + else if (request.Params.Name == "TestElicitationWithDefaults") { var result = await request.Server.ElicitAsync( message: "Please provide information.", @@ -101,7 +101,7 @@ await request.Server.ElicitAsync( } else { - Assert.Fail($"Unexpected tool name: {request.Params!.Name}"); + Assert.Fail($"Unexpected tool name: {request.Params.Name}"); } return new CallToolResult diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs index b463514f9..dec16d6b2 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs @@ -54,7 +54,7 @@ public async Task SupportsMcpServer() Assert.DoesNotContain("server", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []); var result = await prompt.GetAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.NotNull(result.Messages); @@ -83,7 +83,7 @@ public async Task SupportsCtorInjection() }, new() { Services = services }); var result = await prompt.GetAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.NotNull(result.Messages); @@ -133,11 +133,11 @@ public async Task SupportsServiceFromDI() Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []); await Assert.ThrowsAnyAsync(async () => await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken)); var result = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Services = services }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }) { Services = services }, TestContext.Current.CancellationToken); Assert.Equal("Hello", Assert.IsType(result.Messages[0].Content).Text); } @@ -158,7 +158,7 @@ public async Task SupportsOptionalServiceFromDI() }, new() { Services = services }); var result = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("Hello", Assert.IsType(result.Messages[0].Content).Text); } @@ -171,7 +171,7 @@ public async Task SupportsDisposingInstantiatedDisposableTargets() _ => new DisposablePromptType()); var result = await prompt1.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("disposals:1", Assert.IsType(result.Messages[0].Content).Text); } @@ -184,7 +184,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets() _ => new AsyncDisposablePromptType()); var result = await prompt1.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("asyncDisposals:1", Assert.IsType(result.Messages[0].Content).Text); } @@ -197,7 +197,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposable _ => new AsyncDisposableAndDisposablePromptType()); var result = await prompt1.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("disposals:0, asyncDisposals:1", Assert.IsType(result.Messages[0].Content).Text); } @@ -213,7 +213,7 @@ public async Task CanReturnGetPromptResult() }); var actual = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Same(expected, actual); @@ -230,7 +230,7 @@ public async Task CanReturnText() }); var actual = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(actual); @@ -256,7 +256,7 @@ public async Task CanReturnPromptMessage() }); var actual = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(actual); @@ -288,7 +288,7 @@ public async Task CanReturnPromptMessages() }); var actual = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(actual); @@ -315,7 +315,7 @@ public async Task CanReturnChatMessage() }); var actual = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(actual); @@ -347,7 +347,7 @@ public async Task CanReturnChatMessages() }); var actual = await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(actual); @@ -368,7 +368,7 @@ public async Task ThrowsForNullReturn() }); await Assert.ThrowsAsync(async () => await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken)); } @@ -381,7 +381,7 @@ public async Task ThrowsForUnexpectedTypeReturn() }); await Assert.ThrowsAsync(async () => await prompt.GetAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken)); } diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs index 695bbf39d..dfcc0bd4f 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -51,7 +51,7 @@ public void CanCreateServerWithResource() { Contents = [new TextResourceContents { - Uri = ctx.Params!.Uri!, + Uri = ctx.Params.Uri!, Text = "Static Resource", MimeType = "text/plain", }] @@ -87,7 +87,7 @@ public void CanCreateServerWithResourceTemplates() { Contents = [new TextResourceContents { - Uri = ctx.Params!.Uri!, + Uri = ctx.Params.Uri!, Text = "Static Resource", MimeType = "text/plain", }] @@ -111,7 +111,7 @@ public void CreatingReadHandlerWithNoListHandlerSucceeds() { Contents = [new TextResourceContents { - Uri = ctx.Params!.Uri!, + Uri = ctx.Params.Uri!, Text = "Static Resource", MimeType = "text/plain", }] @@ -148,7 +148,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create(() => "42", new() { Name = Name }); Assert.Equal("resource://mcp/Hello", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Hello" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); @@ -156,7 +156,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((McpServer server) => "42", new() { Name = Name }); Assert.Equal("resource://mcp/Hello", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Hello" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); @@ -164,7 +164,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((string arg1) => arg1, new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?arg1}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wOrLd" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?arg1=wOrLd" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("wOrLd", ((TextResourceContents)result.Contents[0]).Text); @@ -172,7 +172,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((string arg1, string? arg2 = null) => arg1 + arg2, new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?arg1,arg2}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wo&arg2=rld" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?arg1=wo&arg2=rld" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("world", ((TextResourceContents)result.Contents[0]).Text); @@ -180,7 +180,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((object a1, bool a2, char a3, byte a4, sbyte a5) => a1.ToString() + a2 + a3 + a4 + a5, new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=hi&a2=true&a3=s&a4=12&a5=34" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=hi&a2=true&a3=s&a4=12&a5=34" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("hiTrues1234", ((TextResourceContents)result.Contents[0]).Text); @@ -188,7 +188,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((ushort a1, short a2, uint a3, int a4, ulong a5) => (a1 + a2 + a3 + a4 + (long)a5).ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text); @@ -196,7 +196,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((long a1, float a2, double a3, decimal a4, TimeSpan a5) => a5.ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text); @@ -204,7 +204,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((DateTime a1, DateTimeOffset a2, Uri a3, Guid a4, Version a5) => a4.ToString("N") + a5, new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a3=http%3A%2F%2Ftest&a4=14e5f43d-0d41-47d6-8207-8249cf669e41&a5=1.2.3.4" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a3=http%3A%2F%2Ftest&a4=14e5f43d-0d41-47d6-8207-8249cf669e41&a5=1.2.3.4" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("14e5f43d0d4147d682078249cf669e411.2.3.4", ((TextResourceContents)result.Contents[0]).Text); @@ -213,7 +213,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((Half a2, Int128 a3, UInt128 a4, IntPtr a5) => (a3 + (Int128)a4 + a5).ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text); @@ -221,7 +221,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((UIntPtr a1, DateOnly a2, TimeOnly a3) => a1.ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text); @@ -230,7 +230,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((bool? a2, char? a3, byte? a4, sbyte? a5) => a2?.ToString() + a3 + a4 + a5, new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a2=true&a3=s&a4=12&a5=34" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a2=true&a3=s&a4=12&a5=34" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("Trues1234", ((TextResourceContents)result.Contents[0]).Text); @@ -238,7 +238,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((ushort? a1, short? a2, uint? a3, int? a4, ulong? a5) => (a1 + a2 + a3 + a4 + (long?)a5).ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text); @@ -246,7 +246,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((long? a1, float? a2, double? a3, decimal? a4, TimeSpan? a5) => a5?.ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text); @@ -254,7 +254,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((DateTime? a1, DateTimeOffset? a2, Guid? a4) => a4?.ToString("N"), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a4}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a4=14e5f43d-0d41-47d6-8207-8249cf669e41" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a4=14e5f43d-0d41-47d6-8207-8249cf669e41" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("14e5f43d0d4147d682078249cf669e41", ((TextResourceContents)result.Contents[0]).Text); @@ -263,7 +263,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((Half? a2, Int128? a3, UInt128? a4, IntPtr? a5) => (a3 + (Int128?)a4 + a5).ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text); @@ -271,7 +271,7 @@ public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() t = McpServerResource.Create((UIntPtr? a1, DateOnly? a2, TimeOnly? a3) => a1?.ToString(), new() { Name = Name }); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate); result = await t.ReadAsync( - new RequestContext(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } }, + new RequestContext(server, CreateTestJsonRpcRequest(), new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text); @@ -288,7 +288,7 @@ public async Task UriTemplate_NonMatchingUri_DoesNotMatch(string uri) Assert.Equal("resource://mcp/Hello{?arg1}", t.ProtocolResourceTemplate.UriTemplate); Assert.False(t.IsMatch(uri)); await Assert.ThrowsAsync(async () => await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = uri } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = uri }), TestContext.Current.CancellationToken)); } @@ -299,7 +299,7 @@ public async Task UriTemplate_IsHostCaseInsensitive(string actualUri, string que { McpServerResource t = McpServerResource.Create(() => "resource", new() { UriTemplate = actualUri }); Assert.NotNull(await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = queriedUri } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = queriedUri }), TestContext.Current.CancellationToken)); } @@ -328,7 +328,7 @@ public async Task UriTemplate_MissingParameter_Throws(string uri) McpServerResource t = McpServerResource.Create((string arg1, int arg2) => arg1, new() { Name = "Hello" }); Assert.Equal("resource://mcp/Hello{?arg1,arg2}", t.ProtocolResourceTemplate.UriTemplate); await Assert.ThrowsAsync(async () => await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = uri } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = uri }), TestContext.Current.CancellationToken)); } @@ -341,25 +341,25 @@ public async Task UriTemplate_MissingOptionalParameter_Succeeds() ReadResourceResult result; result = await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello" } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Hello" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("", ((TextResourceContents)result.Contents[0]).Text); result = await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello?arg1=first" } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Hello?arg1=first" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("first", ((TextResourceContents)result.Contents[0]).Text); result = await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello?arg2=42" } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Hello?arg2=42" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); result = await t.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello?arg1=first&arg2=42" } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Hello?arg1=first&arg2=42" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("first42", ((TextResourceContents)result.Contents[0]).Text); @@ -377,7 +377,7 @@ public async Task SupportsMcpServer() }, new() { Name = "Test" }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); @@ -404,7 +404,7 @@ public async Task SupportsCtorInjection() }, new() { Services = services }); var result = await tool.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "https://something" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "https://something" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.NotNull(result.Contents); @@ -481,11 +481,11 @@ public async Task SupportsServiceFromDI(ServiceLifetime injectedArgumentLifetime Mock mockServer = new(); await Assert.ThrowsAnyAsync(async () => await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken)); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Services = services, Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }) { Services = services }, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); @@ -507,7 +507,7 @@ public async Task SupportsOptionalServiceFromDI() }, new() { Services = services, Name = "Test" }); var result = await resource.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); @@ -523,7 +523,7 @@ public async Task SupportsDisposingInstantiatedDisposableTargets() _ => new DisposableResourceType()); var result = await resource1.ReadAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "test://static/resource/instanceMethod" } }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Uri = "test://static/resource/instanceMethod" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal("0", ((TextResourceContents)result.Contents[0]).Text); @@ -541,7 +541,7 @@ public async Task CanReturnReadResult() return new ReadResourceResult { Contents = [new TextResourceContents { Text = "hello", Uri = "" }] }; }, new() { Name = "Test" }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -558,7 +558,7 @@ public async Task CanReturnResourceContents() return new TextResourceContents { Text = "hello", Uri = "" }; }, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -579,7 +579,7 @@ public async Task CanReturnCollectionOfResourceContents() ]; }, new() { Name = "Test" }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal(2, result.Contents.Count); @@ -597,7 +597,7 @@ public async Task CanReturnString() return "42"; }, new() { Name = "Test" }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -614,7 +614,7 @@ public async Task CanReturnCollectionOfStrings() return new List { "42", "43" }; }, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal(2, result.Contents.Count); @@ -632,7 +632,7 @@ public async Task CanReturnDataContent() return new DataContent(new byte[] { 0, 1, 2 }, "application/octet-stream"); }, new() { Name = "Test" }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -654,7 +654,7 @@ public async Task CanReturnCollectionOfAIContent() }; }, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options }); var result = await resource.ReadAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Uri = "resource://mcp/Test" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Equal(2, result.Contents.Count); diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs index fd62a05c7..431d9133d 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs @@ -62,7 +62,7 @@ public async Task SupportsMcpServer() Assert.DoesNotContain("server", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, McpJsonUtilities.DefaultOptions)); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text); } @@ -88,7 +88,7 @@ public async Task SupportsCtorInjection() }, new() { Services = services }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.NotNull(result.Content); @@ -166,13 +166,13 @@ public async Task SupportsServiceFromDI(ServiceLifetime injectedArgumentLifetime Mock mockServer = new(); var ex = await Assert.ThrowsAsync(async () => await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken)); mockServer.SetupGet(s => s.Services).Returns(services); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) { Services = services }, + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }) { Services = services }, TestContext.Current.CancellationToken); Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text); } @@ -193,7 +193,7 @@ public async Task SupportsOptionalServiceFromDI() }, new() { Services = services }); var result = await tool.InvokeAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text); } @@ -208,7 +208,7 @@ public async Task SupportsDisposingInstantiatedDisposableTargets() options); var result = await tool1.InvokeAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("""{"disposals":1}""", (result.Content[0] as TextContentBlock)?.Text); } @@ -223,7 +223,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets() options); var result = await tool1.InvokeAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()), + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal("""{"asyncDisposals":1}""", (result.Content[0] as TextContentBlock)?.Text); } @@ -242,7 +242,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposable options); var result = await tool1.InvokeAsync( - new RequestContext(new Mock().Object, CreateTestJsonRpcRequest()) { Services = services }, + new RequestContext(new Mock().Object, CreateTestJsonRpcRequest(), new() { Name = "" }) { Services = services }, TestContext.Current.CancellationToken); Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", (result.Content[0] as TextContentBlock)?.Text); } @@ -263,7 +263,7 @@ public async Task CanReturnCollectionOfAIContent() }, new() { SerializerOptions = JsonContext2.Default.Options }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal(3, result.Content.Count); @@ -297,7 +297,7 @@ public async Task CanReturnSingleAIContent(string data, string type) }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Single(result.Content); @@ -333,7 +333,7 @@ public async Task CanReturnNullAIContent() return (string?)null; }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Empty(result.Content); } @@ -348,7 +348,7 @@ public async Task CanReturnString() return "42"; }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Single(result.Content); Assert.Equal("42", Assert.IsType(result.Content[0]).Text); @@ -364,7 +364,7 @@ public async Task CanReturnCollectionOfStrings() return new List { "42", "43" }; }, new() { SerializerOptions = JsonContext2.Default.Options }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Single(result.Content); Assert.Equal("""["42","43"]""", Assert.IsType(result.Content[0]).Text); @@ -380,7 +380,7 @@ public async Task CanReturnMcpContent() return new TextContentBlock { Text = "42" }; }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Single(result.Content); Assert.Equal("42", Assert.IsType(result.Content[0]).Text); @@ -401,7 +401,7 @@ public async Task CanReturnCollectionOfMcpContent() ]; }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Equal(2, result.Content.Count); Assert.Equal("42", Assert.IsType(result.Content[0]).Text); @@ -424,7 +424,7 @@ public async Task CanReturnCallToolResult() return response; }); var result = await tool.InvokeAsync( - new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()), + new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new() { Name = "" }), TestContext.Current.CancellationToken); Assert.Same(response, result); @@ -464,10 +464,7 @@ public async Task StructuredOutput_Enabled_ReturnsExpectedSchema(T value) JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; McpServerTool tool = McpServerTool.Create(() => value, new() { Name = "tool", UseStructuredContent = true, SerializerOptions = options }); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -482,10 +479,7 @@ public async Task StructuredOutput_Enabled_VoidReturningTools_ReturnsExpectedSch { McpServerTool tool = McpServerTool.Create(() => { }); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -493,10 +487,7 @@ public async Task StructuredOutput_Enabled_VoidReturningTools_ReturnsExpectedSch Assert.Null(result.StructuredContent); tool = McpServerTool.Create(() => Task.CompletedTask); - request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -504,10 +495,7 @@ public async Task StructuredOutput_Enabled_VoidReturningTools_ReturnsExpectedSch Assert.Null(result.StructuredContent); tool = McpServerTool.Create(() => default(ValueTask)); - request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -522,10 +510,7 @@ public async Task StructuredOutput_Disabled_ReturnsExpectedSchema(T value) JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; McpServerTool tool = McpServerTool.Create(() => value, new() { UseStructuredContent = false, SerializerOptions = options }); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -823,7 +808,7 @@ public async Task EnablePollingAsync_ThrowsInvalidOperationException_WhenTranspo var jsonRpcRequest = CreateTestJsonRpcRequest(); // The JsonRpcRequest has no Context, so RelatedTransport will be null - var requestContext = new RequestContext(mockServer.Object, jsonRpcRequest); + var requestContext = new RequestContext(mockServer.Object, jsonRpcRequest, new() { Name = "" }); // Act & Assert var exception = await Assert.ThrowsAsync( From db960e6dd97501577c6d4d1cfe78d0ce40ad9aa8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:42:16 +0000 Subject: [PATCH 03/15] Make RequestContext.Params non-nullable Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/a857ef9f-506e-4046-a2c2-d268cfc65701 --- docs/concepts/filters.md | 4 ++-- .../logging/samples/server/Tools/LoggingTools.cs | 2 +- docs/concepts/pagination/pagination.md | 2 +- .../progress/samples/server/Tools/LongRunningTools.cs | 2 +- docs/concepts/resources/resources.md | 4 ++-- samples/EverythingServer/Program.cs | 9 ++------- samples/EverythingServer/Resources/SimpleResourceType.cs | 2 +- samples/EverythingServer/Tools/LongRunningTool.cs | 2 +- 8 files changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/concepts/filters.md b/docs/concepts/filters.md index 9f63dd962..32f5c6757 100644 --- a/docs/concepts/filters.md +++ b/docs/concepts/filters.md @@ -317,7 +317,7 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt { var logger = context.Services?.GetService>(); - logger?.LogInformation($"Processing request from {context.Params?.ProgressToken}"); + logger?.LogInformation($"Processing request from {context.Params.ProgressToken}"); var result = await next(context, cancellationToken); logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools"); return result; @@ -339,7 +339,7 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt catch (Exception ex) { var logger = context.Services?.GetService>(); - logger?.LogError(ex, "Error while processing CallTool request for {ProgressToken}", context.Params?.ProgressToken); + logger?.LogError(ex, "Error while processing CallTool request for {ProgressToken}", context.Params.ProgressToken); return new CallToolResult { diff --git a/docs/concepts/logging/samples/server/Tools/LoggingTools.cs b/docs/concepts/logging/samples/server/Tools/LoggingTools.cs index 33fa3c040..8ddb9e4df 100644 --- a/docs/concepts/logging/samples/server/Tools/LoggingTools.cs +++ b/docs/concepts/logging/samples/server/Tools/LoggingTools.cs @@ -13,7 +13,7 @@ public static async Task LoggingTool( int duration = 10, int steps = 10) { - var progressToken = context.Params?.ProgressToken; + var progressToken = context.Params.ProgressToken; var stepDuration = duration / steps; // diff --git a/docs/concepts/pagination/pagination.md b/docs/concepts/pagination/pagination.md index 1249acbf5..f019fd1fe 100644 --- a/docs/concepts/pagination/pagination.md +++ b/docs/concepts/pagination/pagination.md @@ -77,7 +77,7 @@ builder.Services.AddMcpServer() int startIndex = 0; // Parse cursor to determine starting position - if (ctx.Params?.Cursor is { } cursor) + if (ctx.Params.Cursor is { } cursor) { startIndex = int.Parse(cursor); } diff --git a/docs/concepts/progress/samples/server/Tools/LongRunningTools.cs b/docs/concepts/progress/samples/server/Tools/LongRunningTools.cs index 7fcd1244a..e02889a05 100644 --- a/docs/concepts/progress/samples/server/Tools/LongRunningTools.cs +++ b/docs/concepts/progress/samples/server/Tools/LongRunningTools.cs @@ -15,7 +15,7 @@ public static async Task LongRunningTool( int duration = 10, int steps = 5) { - var progressToken = context.Params?.ProgressToken; + var progressToken = context.Params.ProgressToken; var stepDuration = duration / steps; for (int i = 1; i <= steps; i++) diff --git a/docs/concepts/resources/resources.md b/docs/concepts/resources/resources.md index c7e713c51..5598d96b2 100644 --- a/docs/concepts/resources/resources.md +++ b/docs/concepts/resources/resources.md @@ -212,7 +212,7 @@ builder.Services.AddMcpServer() .WithResources() .WithSubscribeToResourcesHandler(async (ctx, ct) => { - if (ctx.Params?.Uri is { } uri) + if (ctx.Params.Uri is { } uri) { // Track the subscription (e.g., in a concurrent dictionary) subscriptions[ctx.Server.SessionId].TryAdd(uri, 0); @@ -221,7 +221,7 @@ builder.Services.AddMcpServer() }) .WithUnsubscribeFromResourcesHandler(async (ctx, ct) => { - if (ctx.Params?.Uri is { } uri) + if (ctx.Params.Uri is { } uri) { subscriptions[ctx.Server.SessionId].TryRemove(uri, out _); } diff --git a/samples/EverythingServer/Program.cs b/samples/EverythingServer/Program.cs index 99d7759bd..1807fa8de 100644 --- a/samples/EverythingServer/Program.cs +++ b/samples/EverythingServer/Program.cs @@ -130,7 +130,7 @@ { throw new McpException("Cannot add subscription for server with null SessionId"); } - if (ctx.Params?.Uri is { } uri) + if (ctx.Params.Uri is { } uri) { subscriptions[ctx.Server.SessionId].TryAdd(uri, 0); @@ -154,7 +154,7 @@ await ctx.Server.SampleAsync([ { throw new McpException("Cannot remove subscription for server with null SessionId"); } - if (ctx.Params?.Uri is { } uri) + if (ctx.Params.Uri is { } uri) { subscriptions[ctx.Server.SessionId].TryRemove(uri, out _); } @@ -212,11 +212,6 @@ await ctx.Server.SampleAsync([ }) .WithSetLoggingLevelHandler(async (ctx, ct) => { - if (ctx.Params?.Level is null) - { - throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams); - } - // The SDK updates the LoggingLevel field of the IMcpServer await ctx.Server.SendNotificationAsync("notifications/message", new diff --git a/samples/EverythingServer/Resources/SimpleResourceType.cs b/samples/EverythingServer/Resources/SimpleResourceType.cs index eafc6c66d..cc1aed715 100644 --- a/samples/EverythingServer/Resources/SimpleResourceType.cs +++ b/samples/EverythingServer/Resources/SimpleResourceType.cs @@ -19,7 +19,7 @@ public static ResourceContents TemplateResource(RequestContext= ResourceGenerator.Resources.Count) { - throw new NotSupportedException($"Unknown resource: {requestContext.Params?.Uri}"); + throw new NotSupportedException($"Unknown resource: {requestContext.Params.Uri}"); } var resource = ResourceGenerator.Resources[index]; diff --git a/samples/EverythingServer/Tools/LongRunningTool.cs b/samples/EverythingServer/Tools/LongRunningTool.cs index 405b5e823..3dc42b5cf 100644 --- a/samples/EverythingServer/Tools/LongRunningTool.cs +++ b/samples/EverythingServer/Tools/LongRunningTool.cs @@ -15,7 +15,7 @@ public static async Task LongRunningOperation( int duration = 10, int steps = 5) { - var progressToken = context.Params?.ProgressToken; + var progressToken = context.Params.ProgressToken; var stepDuration = duration / steps; for (int i = 1; i <= steps + 1; i++) From eec28fcee58c3e8ceb49501308224a083ca9470a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:57:35 +0000 Subject: [PATCH 04/15] Fix NullReferenceException when request.Params is null in JSON-RPC When a client omits the "params" field from a JSON-RPC message, JsonSerializer.Deserialize returns null. Since RequestContext.Params is now non-nullable, we need to deserialize from an empty JSON object instead to get a valid default TParams instance. Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/1e9aed1a-2748-4117-987c-611e23751bea --- src/ModelContextProtocol.Core/RequestHandlers.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ModelContextProtocol.Core/RequestHandlers.cs b/src/ModelContextProtocol.Core/RequestHandlers.cs index 97e8b95df..d2f81e51e 100644 --- a/src/ModelContextProtocol.Core/RequestHandlers.cs +++ b/src/ModelContextProtocol.Core/RequestHandlers.cs @@ -7,6 +7,8 @@ namespace ModelContextProtocol; internal sealed class RequestHandlers : Dictionary>> { + private static readonly JsonNode EmptyJsonObject = JsonNode.Parse("{}")!; + /// /// Registers a handler for incoming requests of a specific method in the MCP protocol. /// @@ -40,7 +42,10 @@ public void Set( this[method] = async (request, cancellationToken) => { - TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!; + // When request.Params is null (e.g. the client omitted "params" from the JSON-RPC message), + // deserialize from an empty JSON object so we get a valid default TParams instance + // rather than null, since RequestContext.Params is now non-nullable. + TParams typedRequest = JsonSerializer.Deserialize(request.Params ?? EmptyJsonObject, requestTypeInfo)!; object? result = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false); return JsonSerializer.SerializeToNode(result, responseTypeInfo); }; From c0f7088a995dbd419169fac6d9d841b2cea50ef1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 04:18:33 +0000 Subject: [PATCH 05/15] Revert null-conditional removal in McpServerImpl.cs; keep only InvokeHandlerAsync constructor change The previous commits removed null-conditional operators (?.) from handler lambdas in McpServerImpl.cs, but at runtime TParams can still be null when JSON-RPC params is missing. Also reverts the EmptyJsonObject fallback in RequestHandlers.cs which failed for types with required constructor params. Restores defensive null-conditional access in all handler lambdas while keeping the desired API changes: TParams (non-nullable) InvokeHandlerAsync signature and the new 3-arg RequestContext constructor. Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/74aa9308-d4cf-4669-86b4-1a680d0676b4 --- .../RequestHandlers.cs | 7 +- .../Server/McpServerImpl.cs | 73 ++++++++++--------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/ModelContextProtocol.Core/RequestHandlers.cs b/src/ModelContextProtocol.Core/RequestHandlers.cs index d2f81e51e..97e8b95df 100644 --- a/src/ModelContextProtocol.Core/RequestHandlers.cs +++ b/src/ModelContextProtocol.Core/RequestHandlers.cs @@ -7,8 +7,6 @@ namespace ModelContextProtocol; internal sealed class RequestHandlers : Dictionary>> { - private static readonly JsonNode EmptyJsonObject = JsonNode.Parse("{}")!; - /// /// Registers a handler for incoming requests of a specific method in the MCP protocol. /// @@ -42,10 +40,7 @@ public void Set( this[method] = async (request, cancellationToken) => { - // When request.Params is null (e.g. the client omitted "params" from the JSON-RPC message), - // deserialize from an empty JSON object so we get a valid default TParams instance - // rather than null, since RequestContext.Params is now non-nullable. - TParams typedRequest = JsonSerializer.Deserialize(request.Params ?? EmptyJsonObject, requestTypeInfo)!; + TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!; object? result = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false); return JsonSerializer.SerializeToNode(result, responseTypeInfo); }; diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 06fa965e1..c9a6f764c 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -208,8 +208,8 @@ private void ConfigureInitialize(McpServerOptions options) _requestHandlers.Set(RequestMethods.Initialize, async (request, _, _) => { - _clientCapabilities = request.Capabilities ?? new(); - _clientInfo = request.ClientInfo; + _clientCapabilities = request?.Capabilities ?? new(); + _clientInfo = request?.ClientInfo; // Use the ClientInfo to update the session EndpointName for logging. UpdateEndpointNameWithClientInfo(); @@ -219,7 +219,7 @@ private void ConfigureInitialize(McpServerOptions options) // Otherwise, try to use whatever the client requested as long as it's supported. // If it's not supported, fall back to the latest supported version. string? protocolVersion = options.ProtocolVersion; - protocolVersion ??= request.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ? + protocolVersion ??= request?.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ? clientProtocolVersion : McpSessionHandler.LatestProtocolVersion; @@ -266,7 +266,7 @@ private void ConfigureCompletion(McpServerOptions options) CompleteResult result = await originalCompleteHandler(request, cancellationToken).ConfigureAwait(false); string[]? allowedValues = null; - switch (request.Params.Ref) + switch (request.Params?.Ref) { case PromptReference pr when promptCompletions is not null: if (promptCompletions.TryGetValue(pr.Name, out var promptParams)) @@ -285,7 +285,7 @@ private void ConfigureCompletion(McpServerOptions options) if (allowedValues is not null) { - string partialValue = request.Params.Argument.Value; + string partialValue = request.Params!.Argument.Value; foreach (var v in allowedValues) { if (v.StartsWith(partialValue, StringComparison.OrdinalIgnoreCase)) @@ -409,7 +409,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null && listResourcesHandler ??= (static async (_, __) => new ListResourcesResult()); listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult()); - readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound)); + readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.ResourceNotFound)); subscribeHandler ??= (static async (_, __) => new EmptyResult()); unsubscribeHandler ??= (static async (_, __) => new EmptyResult()); var listChanged = resourcesCapability?.ListChanged; @@ -425,7 +425,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null && await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params.Cursor is null) + if (request.Params?.Cursor is null) { foreach (var r in resources) { @@ -446,7 +446,7 @@ await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(fa await originalListResourceTemplatesHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params.Cursor is null) + if (request.Params?.Cursor is null) { foreach (var rt in resources) { @@ -484,7 +484,7 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure async (request, cancellationToken) => { // Initial handler that sets MatchedPrimitive - if (request.Params.Uri is { } uri && resources is not null) + if (request.Params?.Uri is { } uri && resources is not null) { // First try an O(1) lookup by exact match. if (resources.TryGetPrimitive(uri, out var resource) && !resource.IsTemplated) @@ -508,12 +508,12 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure try { var result = await handler(request, cancellationToken).ConfigureAwait(false); - ReadResourceCompleted(request.Params.Uri ?? string.Empty); + ReadResourceCompleted(request.Params?.Uri ?? string.Empty); return result; } catch (Exception e) { - ReadResourceError(request.Params.Uri ?? string.Empty, e); + ReadResourceError(request.Params?.Uri ?? string.Empty, e); throw; } }); @@ -570,7 +570,7 @@ private void ConfigurePrompts(McpServerOptions options) ServerCapabilities.Prompts = new(); listPromptsHandler ??= (static async (_, __) => new ListPromptsResult()); - getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params.Name}'", McpErrorCode.InvalidParams)); + getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams)); var listChanged = promptsCapability?.ListChanged; // Handle tools provided via DI by augmenting the handlers to incorporate them. @@ -583,7 +583,7 @@ private void ConfigurePrompts(McpServerOptions options) await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params.Cursor is null) + if (request.Params?.Cursor is null) { foreach (var p in prompts) { @@ -613,7 +613,7 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals async (request, cancellationToken) => { // Initial handler that sets MatchedPrimitive - if (request.Params.Name is { } promptName && prompts is not null && + if (request.Params?.Name is { } promptName && prompts is not null && prompts.TryGetPrimitive(promptName, out var prompt)) { request.MatchedPrimitive = prompt; @@ -622,12 +622,12 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals try { var result = await handler(request, cancellationToken).ConfigureAwait(false); - GetPromptCompleted(request.Params.Name ?? string.Empty); + GetPromptCompleted(request.Params?.Name ?? string.Empty); return result; } catch (Exception e) { - GetPromptError(request.Params.Name ?? string.Empty, e); + GetPromptError(request.Params?.Name ?? string.Empty, e); throw; } }); @@ -663,7 +663,7 @@ private void ConfigureTools(McpServerOptions options) ServerCapabilities.Tools = new(); listToolsHandler ??= (static async (_, __) => new ListToolsResult()); - callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams)); + callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams)); var listChanged = toolsCapability?.ListChanged; // Handle tools provided via DI by augmenting the handlers to incorporate them. @@ -676,7 +676,7 @@ private void ConfigureTools(McpServerOptions options) await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) : new(); - if (request.Params.Cursor is null) + if (request.Params?.Cursor is null) { foreach (var t in tools) { @@ -697,7 +697,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) var taskSupport = tool.ProtocolTool.Execution?.TaskSupport ?? ToolTaskSupport.Forbidden; // Check if this is a task-augmented request - if (request.Params.Task is { } taskMetadata) + if (request.Params?.Task is { } taskMetadata) { // Validate tool-level task support if (taskSupport is ToolTaskSupport.Forbidden) @@ -735,7 +735,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) async (request, cancellationToken) => { // Initial handler that sets MatchedPrimitive - if (request.Params.Name is { } toolName && tools is not null && + if (request.Params?.Name is { } toolName && tools is not null && tools.TryGetPrimitive(toolName, out var tool)) { request.MatchedPrimitive = tool; @@ -749,14 +749,14 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) // in ExecuteToolAsTaskAsync when the tool actually completes. if (result.Task is null) { - ToolCallCompleted(request.Params.Name ?? string.Empty, result.IsError is true); + ToolCallCompleted(request.Params?.Name ?? string.Empty, result.IsError is true); } return result; } catch (Exception e) { - ToolCallError(request.Params.Name ?? string.Empty, e); + ToolCallError(request.Params?.Name ?? string.Empty, e); if ((e is OperationCanceledException && cancellationToken.IsCancellationRequested) || e is McpProtocolException) { @@ -769,8 +769,8 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) Content = [new TextContentBlock { Text = e is McpException ? - $"An error occurred invoking '{request.Params.Name}': {e.Message}" : - $"An error occurred invoking '{request.Params.Name}'.", + $"An error occurred invoking '{request.Params?.Name}': {e.Message}" : + $"An error occurred invoking '{request.Params?.Name}'.", }], }; } @@ -818,7 +818,7 @@ private void ConfigureTasks(McpServerOptions options) // tasks/get handler - Retrieve task status McpRequestHandler getTaskHandler = async (request, cancellationToken) => { - if (request.Params.TaskId is not { } taskId) + if (request.Params?.TaskId is not { } taskId) { throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams); } @@ -839,7 +839,7 @@ private void ConfigureTasks(McpServerOptions options) async Task GetTaskResultAsync(RequestContext request, CancellationToken cancellationToken) { - if (request.Params.TaskId is not { } taskId) + if (request.Params?.TaskId is not { } taskId) { throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams); } @@ -872,14 +872,14 @@ async Task GetTaskResultAsync(RequestContext listTasksHandler = async (request, cancellationToken) => { - var cursor = request.Params.Cursor; + var cursor = request.Params?.Cursor; return await taskStore.ListTasksAsync(cursor, SessionId, cancellationToken).ConfigureAwait(false); }; // tasks/cancel handler - Cancel a task McpRequestHandler cancelTaskHandler = async (request, cancellationToken) => { - if (request.Params.TaskId is not { } taskId) + if (request.Params?.TaskId is not { } taskId) { throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams); } @@ -941,17 +941,20 @@ private void ConfigureLogging(McpServerOptions options) (request, jsonRpcRequest, cancellationToken) => { // Store the provided level. - if (_loggingLevel is null) + if (request is not null) { - Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null); - } + if (_loggingLevel is null) + { + Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null); + } - _loggingLevel.Value = request.Level; + _loggingLevel.Value = request.Level; + } // If a handler was provided, now delegate to it. if (setLoggingLevelHandler is not null) { - return InvokeHandlerAsync(setLoggingLevelHandler, request, jsonRpcRequest, cancellationToken); + return InvokeHandlerAsync(setLoggingLevelHandler, request!, jsonRpcRequest, cancellationToken); } // Otherwise, consider it handled. @@ -1163,7 +1166,7 @@ private async ValueTask ExecuteToolAsTaskAsync( // Invoke the tool with task-specific cancellation token var result = await tool.InvokeAsync(request, taskCancellationToken).ConfigureAwait(false); - ToolCallCompleted(request.Params.Name ?? string.Empty, result.IsError is true); + ToolCallCompleted(request.Params?.Name ?? string.Empty, result.IsError is true); // Determine final status based on whether there was an error var finalStatus = result.IsError is true ? McpTaskStatus.Failed : McpTaskStatus.Completed; @@ -1192,7 +1195,7 @@ private async ValueTask ExecuteToolAsTaskAsync( catch (Exception ex) { // Log the error - ToolCallError(request.Params.Name ?? string.Empty, ex); + ToolCallError(request.Params?.Name ?? string.Empty, ex); // Store error result var errorResult = new CallToolResult From a2027b55399a494b6bd605ce030b2b7386657abb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 07:34:09 +0000 Subject: [PATCH 06/15] Rename @params to parameters and add MCP9003 diagnostic for obsolete constructor - Rename the `@params` constructor parameter to `parameters` in the 3-arg RequestContext constructor. - Add MCP9003 diagnostic ID to Obsoletions.cs for the obsolete 2-arg constructor, following the established pattern (Message, DiagnosticId, UrlFormat). - Update the [Obsolete] attribute to use the centralized constants with DiagnosticId and UrlFormat. - Document MCP9003 in docs/list-of-diagnostics.md. Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/6e17cd63-6cb0-4d77-ba6c-1f2a616fa03f --- docs/list-of-diagnostics.md | 1 + src/Common/Obsoletions.cs | 4 ++++ src/ModelContextProtocol.Core/Server/RequestContext.cs | 8 ++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md index 59666d518..16f17f7ed 100644 --- a/docs/list-of-diagnostics.md +++ b/docs/list-of-diagnostics.md @@ -36,3 +36,4 @@ When APIs are marked as obsolete, a diagnostic is emitted to warn users that the | :------------ | :----- | :---------- | | `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()`. | +| `MCP9003` | In place | The `RequestContext(McpServer, JsonRpcRequest)` constructor is obsolete. Use the overload that accepts a `parameters` argument: `RequestContext(McpServer, JsonRpcRequest, TParams)`. | diff --git a/src/Common/Obsoletions.cs b/src/Common/Obsoletions.cs index bbc7bffb4..cc850d126 100644 --- a/src/Common/Obsoletions.cs +++ b/src/Common/Obsoletions.cs @@ -25,4 +25,8 @@ internal static class Obsoletions // MCP9002 was used for the AddXxxFilter extension methods on IMcpServerBuilder that were superseded by // WithMessageFilters() and WithRequestFilters(). The APIs were removed; do not reuse this diagnostic ID. + + public const string RequestContextParamsConstructor_DiagnosticId = "MCP9003"; + public const string RequestContextParamsConstructor_Message = "Use the constructor overload that accepts a parameters argument."; + public const string RequestContextParamsConstructor_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcp9003"; } diff --git a/src/ModelContextProtocol.Core/Server/RequestContext.cs b/src/ModelContextProtocol.Core/Server/RequestContext.cs index 4a8168542..596712eea 100644 --- a/src/ModelContextProtocol.Core/Server/RequestContext.cs +++ b/src/ModelContextProtocol.Core/Server/RequestContext.cs @@ -18,12 +18,12 @@ public sealed class RequestContext : MessageContext /// /// The server with which this instance is associated. /// The JSON-RPC request associated with this context. - /// The parameters associated with this request. + /// The parameters associated with this request. /// or is . - public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams @params) + public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams parameters) : base(server, jsonRpcRequest) { - Params = @params; + Params = parameters; } /// @@ -32,7 +32,7 @@ public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams @ /// The server with which this instance is associated. /// The JSON-RPC request associated with this context. /// or is . - [Obsolete("Use the constructor that accepts a params argument.")] + [Obsolete(Obsoletions.RequestContextParamsConstructor_Message, DiagnosticId = Obsoletions.RequestContextParamsConstructor_DiagnosticId, UrlFormat = Obsoletions.RequestContextParamsConstructor_Url)] public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest) : base(server, jsonRpcRequest) { From bbe96faba2e36bc900f4d75827db01c09a70ce32 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:19:29 -0400 Subject: [PATCH 07/15] Support specifying OutputSchema independently of return type for tools returning CallToolResult (#1425) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Server/AIFunctionMcpServerTool.cs | 19 +- .../Server/McpServerToolAttribute.cs | 21 ++ .../Server/McpServerToolCreateOptions.cs | 21 ++ .../Server/McpServerToolTests.cs | 275 ++++++++++++++++++ 4 files changed, 335 insertions(+), 1 deletion(-) diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs index cf7d9925f..3cb5c660a 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs @@ -206,6 +206,13 @@ private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpSe newOptions.UseStructuredContent = toolAttr.UseStructuredContent; + if (toolAttr.OutputSchemaType is Type outputSchemaType) + { + newOptions.OutputSchema ??= AIJsonUtilities.CreateJsonSchema(outputSchemaType, + serializerOptions: newOptions.SerializerOptions ?? McpJsonUtilities.DefaultOptions, + inferenceOptions: newOptions.SchemaCreateOptions); + } + if (toolAttr._taskSupport is { } taskSupport) { newOptions.Execution ??= new ToolExecution(); @@ -487,7 +494,17 @@ schema.ValueKind is not JsonValueKind.Object || return null; } - if (function.ReturnJsonSchema is not JsonElement outputSchema) + // Explicit OutputSchema takes precedence over AIFunction's return schema. + JsonElement outputSchema; + if (toolCreateOptions.OutputSchema is { } explicitSchema) + { + outputSchema = explicitSchema; + } + else if (function.ReturnJsonSchema is { } returnSchema) + { + outputSchema = returnSchema; + } + else { return null; } diff --git a/src/ModelContextProtocol.Core/Server/McpServerToolAttribute.cs b/src/ModelContextProtocol.Core/Server/McpServerToolAttribute.cs index 21a227e8f..d67bac18c 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerToolAttribute.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerToolAttribute.cs @@ -265,6 +265,27 @@ public bool ReadOnly /// public bool UseStructuredContent { get; set; } + /// + /// Gets or sets a from which to generate the tool's output schema. + /// + /// + /// The default is , which means the output schema is inferred from the return type. + /// + /// + /// + /// When set, a JSON schema is generated from the specified and used as the + /// instead of the schema inferred from the tool method's return type. + /// This is particularly useful when a tool method returns directly + /// (to control properties like , , + /// or ) but still needs to advertise a meaningful output + /// schema to clients. + /// + /// + /// must also be set to for this property to take effect. + /// + /// + public Type? OutputSchemaType { get; set; } + /// /// Gets or sets the source URI for the tool's icon. /// diff --git a/src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs b/src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs index 3bf0c5305..88d718d13 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs @@ -129,6 +129,26 @@ public sealed class McpServerToolCreateOptions /// public bool UseStructuredContent { get; set; } + /// + /// Gets or sets an explicit JSON schema to use as the tool's output schema. + /// + /// + /// The default is , which means the output schema is inferred from the return type. + /// + /// + /// + /// When set, this schema is used as the instead of the schema + /// inferred from the tool method's return type. This is particularly useful when a tool method + /// returns directly (to control properties like , + /// , or ) but still + /// needs to advertise a meaningful output schema to clients. + /// + /// + /// must also be set to for this property to take effect. + /// + /// + public JsonElement? OutputSchema { get; set; } + /// /// Gets or sets the JSON serializer options to use when marshalling data to/from JSON. /// @@ -209,6 +229,7 @@ internal McpServerToolCreateOptions Clone() => OpenWorld = OpenWorld, ReadOnly = ReadOnly, UseStructuredContent = UseStructuredContent, + OutputSchema = OutputSchema, SerializerOptions = SerializerOptions, SchemaCreateOptions = SchemaCreateOptions, Metadata = Metadata, diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs index 431d9133d..050aa7b16 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs @@ -518,6 +518,273 @@ public async Task StructuredOutput_Disabled_ReturnsExpectedSchema(T value) Assert.Null(result.StructuredContent); } + [Fact] + public void OutputSchema_Options_OverridesReturnTypeSchema() + { + // When OutputSchema is set on options, it should be used instead of the return type's schema + JsonElement outputSchema = JsonDocument.Parse("""{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"}},"required":["name","age"]}""").RootElement; + McpServerTool tool = McpServerTool.Create(() => "result", new() + { + UseStructuredContent = true, + OutputSchema = outputSchema, + }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties)); + Assert.True(properties.TryGetProperty("name", out _)); + Assert.True(properties.TryGetProperty("age", out _)); + } + + [Fact] + public void OutputSchema_Options_WithCallToolResultReturn() + { + // When the tool returns CallToolResult, OutputSchema on options provides the advertised schema + JsonElement outputSchema = JsonDocument.Parse("""{"type":"object","properties":{"result":{"type":"string"}},"required":["result"]}""").RootElement; + McpServerTool tool = McpServerTool.Create(() => new CallToolResult() { Content = [] }, new() + { + UseStructuredContent = true, + OutputSchema = outputSchema, + }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString()); + Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties)); + Assert.True(properties.TryGetProperty("result", out _)); + } + + [Fact] + public async Task OutputSchema_Options_CallToolResult_PreservesStructuredContent() + { + // When tool returns CallToolResult with StructuredContent, it's preserved in the response + JsonElement outputSchema = JsonDocument.Parse("""{"type":"object","properties":{"value":{"type":"integer"}},"required":["value"]}""").RootElement; + JsonElement structuredContent = JsonDocument.Parse("""{"value":42}""").RootElement; + McpServerTool tool = McpServerTool.Create(() => new CallToolResult() + { + Content = [new TextContentBlock { Text = "42" }], + StructuredContent = structuredContent, + }, new() + { + Name = "tool", + UseStructuredContent = true, + OutputSchema = outputSchema, + }); + var mockServer = new Mock(); + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) + { + Params = new CallToolRequestParams { Name = "tool" }, + }; + + var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.NotNull(result.StructuredContent); + Assert.Equal(42, result.StructuredContent.Value.GetProperty("value").GetInt32()); + AssertMatchesJsonSchema(tool.ProtocolTool.OutputSchema.Value, result.StructuredContent); + } + + [Fact] + public void OutputSchema_Options_RequiresUseStructuredContent() + { + // OutputSchema without UseStructuredContent=true should not produce an output schema + JsonElement outputSchema = JsonDocument.Parse("""{"type":"object","properties":{"name":{"type":"string"}}}""").RootElement; + McpServerTool tool = McpServerTool.Create(() => "result", new() + { + UseStructuredContent = false, + OutputSchema = outputSchema, + }); + + Assert.Null(tool.ProtocolTool.OutputSchema); + } + + [Fact] + public void OutputSchema_Options_NonObjectSchema_GetsWrapped() + { + // Non-object output schema should be wrapped in a "result" property envelope + JsonElement outputSchema = JsonDocument.Parse("""{"type":"string"}""").RootElement; + McpServerTool tool = McpServerTool.Create(() => "result", new() + { + UseStructuredContent = true, + OutputSchema = outputSchema, + }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString()); + Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties)); + Assert.True(properties.TryGetProperty("result", out var resultProp)); + Assert.Equal("string", resultProp.GetProperty("type").GetString()); + } + + [Fact] + public void OutputSchema_Options_NullableObjectSchema_BecomesObject() + { + // ["object", "null"] type should be simplified to just "object" + JsonElement outputSchema = JsonDocument.Parse("""{"type":["object","null"],"properties":{"name":{"type":"string"}}}""").RootElement; + McpServerTool tool = McpServerTool.Create(() => "result", new() + { + UseStructuredContent = true, + OutputSchema = outputSchema, + }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString()); + } + + [Fact] + public void OutputSchema_Attribute_WithType_GeneratesSchema() + { + McpServerTool tool = McpServerTool.Create(ToolWithOutputSchemaAttribute, new() { SerializerOptions = CreateSerializerOptionsWithPerson() }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString()); + Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties)); + Assert.True(properties.TryGetProperty("name", out _)); + Assert.True(properties.TryGetProperty("age", out _)); + } + + [Fact] + public async Task OutputSchema_Attribute_CallToolResult_PreservesStructuredContent() + { + McpServerTool tool = McpServerTool.Create(ToolWithOutputSchemaAttribute, new() { SerializerOptions = CreateSerializerOptionsWithPerson() }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString()); + + var mockServer = new Mock(); + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) + { + Params = new CallToolRequestParams { Name = "tool" }, + }; + + var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); + + Assert.NotNull(result.StructuredContent); + Assert.Equal("John", result.StructuredContent.Value.GetProperty("name").GetString()); + Assert.Equal(27, result.StructuredContent.Value.GetProperty("age").GetInt32()); + AssertMatchesJsonSchema(tool.ProtocolTool.OutputSchema.Value, result.StructuredContent); + } + + [Fact] + public void OutputSchema_Attribute_WithoutUseStructuredContent_NoSchema() + { + // If UseStructuredContent is false but OutputSchema type is set, no output schema should be generated + McpServerTool tool = McpServerTool.Create(ToolWithOutputSchemaButNoStructuredContent, new() { SerializerOptions = CreateSerializerOptionsWithPerson() }); + + Assert.Null(tool.ProtocolTool.OutputSchema); + } + + [Fact] + public void OutputSchema_Options_TakesPrecedenceOverAttribute() + { + // Options.OutputSchema should take precedence over attribute-derived schema + JsonElement outputSchema = JsonDocument.Parse("""{"type":"object","properties":{"custom":{"type":"boolean"}},"required":["custom"]}""").RootElement; + McpServerTool tool = McpServerTool.Create(ToolWithOutputSchemaAttribute, new() + { + OutputSchema = outputSchema, + }); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties)); + Assert.True(properties.TryGetProperty("custom", out _)); + // Should not have Person's properties + Assert.False(properties.TryGetProperty("name", out _)); + } + + [Fact] + public void OutputSchema_Options_Clone_PreservesValue() + { + // Verify that Clone() preserves the OutputSchema property + JsonElement outputSchema = JsonDocument.Parse("""{"type":"object","properties":{"x":{"type":"integer"}}}""").RootElement; + McpServerTool tool1 = McpServerTool.Create(() => "result", new() + { + Name = "tool1", + UseStructuredContent = true, + OutputSchema = outputSchema, + }); + + // The output schema should be present since we set it + Assert.NotNull(tool1.ProtocolTool.OutputSchema); + Assert.True(tool1.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var props)); + Assert.True(props.TryGetProperty("x", out _)); + } + + [Fact] + public async Task OutputSchema_Options_PersonType_WithCallToolResult() + { + // Create output schema from Person type, tool returns CallToolResult with matching structured content + JsonSerializerOptions serializerOptions = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; + JsonElement outputSchema = AIJsonUtilities.CreateJsonSchema(typeof(Person), serializerOptions: serializerOptions); + Person person = new("Alice", 30); + JsonElement structuredContent = JsonSerializer.SerializeToElement(person, serializerOptions); + McpServerTool tool = McpServerTool.Create(() => new CallToolResult() + { + Content = [new TextContentBlock { Text = "Alice, 30" }], + StructuredContent = structuredContent, + }, new() + { + Name = "tool", + UseStructuredContent = true, + OutputSchema = outputSchema, + SerializerOptions = serializerOptions, + }); + var mockServer = new Mock(); + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) + { + Params = new CallToolRequestParams { Name = "tool" }, + }; + + var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.NotNull(result.StructuredContent); + AssertMatchesJsonSchema(tool.ProtocolTool.OutputSchema.Value, result.StructuredContent); + } + + [Fact] + public async Task OutputSchema_Options_OverridesReturnTypeSchema_InvokeAndValidate() + { + // OutputSchema overrides return type schema; result should match the original return type, but schema is the override + JsonSerializerOptions serializerOptions = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; + JsonElement outputSchema = AIJsonUtilities.CreateJsonSchema(typeof(Person), serializerOptions: serializerOptions); + McpServerTool tool = McpServerTool.Create(() => new Person("Bob", 25), new() + { + Name = "tool", + UseStructuredContent = true, + OutputSchema = outputSchema, + SerializerOptions = serializerOptions, + }); + var mockServer = new Mock(); + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) + { + Params = new CallToolRequestParams { Name = "tool" }, + }; + + var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); + + Assert.NotNull(tool.ProtocolTool.OutputSchema); + Assert.NotNull(result.StructuredContent); + AssertMatchesJsonSchema(tool.ProtocolTool.OutputSchema.Value, result.StructuredContent); + } + + [McpServerTool(UseStructuredContent = true, OutputSchemaType = typeof(Person))] + private static CallToolResult ToolWithOutputSchemaAttribute() + { + var person = new Person("John", 27); + return new CallToolResult() + { + Content = [new TextContentBlock { Text = $"{person.Name}, {person.Age}" }], + StructuredContent = JsonSerializer.SerializeToElement(person, JsonContext2.Default.Person), + }; + } + + [McpServerTool(UseStructuredContent = false, OutputSchemaType = typeof(Person))] + private static CallToolResult ToolWithOutputSchemaButNoStructuredContent() + { + return new CallToolResult() + { + Content = [new TextContentBlock { Text = "result" }], + }; + } + [Theory] [InlineData(JsonNumberHandling.Strict)] [InlineData(JsonNumberHandling.AllowReadingFromString)] @@ -664,6 +931,13 @@ Instance JSON document does not match the specified schema. record Person(string Name, int Age); + private static JsonSerializerOptions CreateSerializerOptionsWithPerson() + { + JsonSerializerOptions options = new(McpJsonUtilities.DefaultOptions); + options.TypeInfoResolverChain.Add(JsonContext2.Default); + return options; + } + [Fact] public void SupportsIconsInCreateOptions() { @@ -913,5 +1187,6 @@ private static string SyncTool() [JsonSerializable(typeof(List))] [JsonSerializable(typeof(int?))] [JsonSerializable(typeof(DateTimeOffset?))] + [JsonSerializable(typeof(Person))] partial class JsonContext2 : JsonSerializerContext; } From 6586519a66736b720e6c256c768b091d297c24a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:20:12 -0400 Subject: [PATCH 08/15] Bump the other-testing group with 2 updates (#1440) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2a8fff7e6..937b2a3b5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,7 +3,7 @@ true 8.0.22 9.0.11 - 10.0.4 + 10.0.5 10.4.0 @@ -92,6 +92,6 @@ - + \ No newline at end of file From 1f54ed0405ce0add3ac98faad2034167d0c5d0ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:37:15 -0700 Subject: [PATCH 09/15] Bump danielpalme/ReportGenerator-GitHub-Action from 5.5.2 to 5.5.4 (#1438) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stephen Toub --- .github/workflows/ci-code-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-code-coverage.yml b/.github/workflows/ci-code-coverage.yml index 88b31de11..c4d4a052e 100644 --- a/.github/workflows/ci-code-coverage.yml +++ b/.github/workflows/ci-code-coverage.yml @@ -24,7 +24,7 @@ jobs: pattern: testresults-* - name: Combine coverage reports - uses: danielpalme/ReportGenerator-GitHub-Action@5.5.2 + uses: danielpalme/ReportGenerator-GitHub-Action@5.5.4 with: reports: "**/*.cobertura.xml" targetdir: "${{ github.workspace }}/report" From e170147738f681e25bda028f2dcba815ab0c7b2e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 23 Mar 2026 07:17:51 -0400 Subject: [PATCH 10/15] Update Microsoft.Extensions.AI.OpenAI version reference (#1456) --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 937b2a3b5..2f594a2b0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -67,7 +67,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + @@ -94,4 +94,4 @@ - \ No newline at end of file + From 70d8628b07e8a85ef7601999287fd04b8898e00d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:21:28 +0200 Subject: [PATCH 11/15] Bump Microsoft.Extensions.AI from 10.4.0 to 10.4.1 (#1461) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2f594a2b0..c5bbfe51a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ 8.0.22 9.0.11 10.0.5 - 10.4.0 + 10.4.1 From a81876371aecfa5923e724bc6437057eb30d7818 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:21:34 +0200 Subject: [PATCH 12/15] Bump Anthropic from 12.8.0 to 12.9.0 (#1460) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c5bbfe51a..c45dbaab0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -62,7 +62,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 4bffdd11f6f13e97b2b582c176834968c302eeb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 19:08:45 +0000 Subject: [PATCH 13/15] Add 3-arg RequestContext constructor and obsolete 2-arg to eliminate null-forgiving operator usage Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/43f266c7-392e-40c4-a808-759fb46d04d5 --- .../Server/McpServerToolTests.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs index 050aa7b16..808ba7efe 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs @@ -569,10 +569,7 @@ public async Task OutputSchema_Options_CallToolResult_PreservesStructuredContent OutputSchema = outputSchema, }); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -650,10 +647,7 @@ public async Task OutputSchema_Attribute_CallToolResult_PreservesStructuredConte Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString()); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -727,10 +721,7 @@ public async Task OutputSchema_Options_PersonType_WithCallToolResult() SerializerOptions = serializerOptions, }); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); @@ -753,10 +744,7 @@ public async Task OutputSchema_Options_OverridesReturnTypeSchema_InvokeAndValida SerializerOptions = serializerOptions, }); var mockServer = new Mock(); - var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest()) - { - Params = new CallToolRequestParams { Name = "tool" }, - }; + var request = new RequestContext(mockServer.Object, CreateTestJsonRpcRequest(), new CallToolRequestParams { Name = "tool" }); var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken); From cf13c6b71c49f7c1eae71d280021c23f2094ece4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:26:40 +0000 Subject: [PATCH 14/15] Restore null conditional/forgiving operators on Params member access in src/ Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/57676fa6-299c-40eb-ba0b-a7cbedbcef91 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Server/AIFunctionMcpServerPrompt.cs | 2 +- .../Server/AIFunctionMcpServerResource.cs | 12 ++++++------ .../Server/AIFunctionMcpServerTool.cs | 2 +- .../Server/RequestServiceProvider.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs index 30f31c308..c755cd9e5 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerPrompt.cs @@ -201,7 +201,7 @@ public override async ValueTask GetAsync( request.Services = new RequestServiceProvider(request); AIFunctionArguments arguments = new() { Services = request.Services }; - if (request.Params.Arguments is { } argDict) + if (request.Params?.Arguments is { } argDict) { foreach (var kvp in argDict) { diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs index 7f0f29140..3413d7038 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs @@ -386,14 +386,14 @@ public override async ValueTask ReadAsync( TextContent tc => new() { - Contents = [new TextResourceContents { Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }], + Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }], }, DataContent dc => new() { Contents = [new BlobResourceContents { - Uri = request.Params.Uri, + Uri = request.Params!.Uri, MimeType = dc.MediaType, Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span) }], @@ -401,7 +401,7 @@ public override async ValueTask ReadAsync( string text => new() { - Contents = [new TextResourceContents { Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }], + Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }], }, IEnumerable contents => new() @@ -416,14 +416,14 @@ public override async ValueTask ReadAsync( { TextContent tc => new TextResourceContents { - Uri = request.Params.Uri, + Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }, DataContent dc => new BlobResourceContents { - Uri = request.Params.Uri, + Uri = request.Params!.Uri, MimeType = dc.MediaType, Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span) }, @@ -436,7 +436,7 @@ public override async ValueTask ReadAsync( { Contents = strings.Select(text => new TextResourceContents { - Uri = request.Params.Uri, + Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }).ToList(), diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs index 3cb5c660a..700d9d26d 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs @@ -262,7 +262,7 @@ public override async ValueTask InvokeAsync( request.Services = new RequestServiceProvider(request); AIFunctionArguments arguments = new() { Services = request.Services }; - if (request.Params.Arguments is { } argDict) + if (request.Params?.Arguments is { } argDict) { foreach (var kvp in argDict) { diff --git a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs index 0c3da62e2..86f0a6884 100644 --- a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs +++ b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs @@ -27,7 +27,7 @@ public static bool IsAugmentedWith(Type serviceType) => serviceType == typeof(RequestContext) ? request : serviceType == typeof(McpServer) ? request.Server : serviceType == typeof(IProgress) ? - (request.Params.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) : + (request.Params?.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) : serviceType == typeof(ClaimsPrincipal) ? request.User : _innerServices?.GetService(serviceType); From 650dd37b9119f73b062e58554e35c71de97c397d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:33:04 +0000 Subject: [PATCH 15/15] Remove unnecessary null-forgiving operators on Params (keep only null-conditionals) Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/e4a931bf-4543-4726-8897-54dcc66e354d Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../Server/AIFunctionMcpServerResource.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs index 3413d7038..7f0f29140 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerResource.cs @@ -386,14 +386,14 @@ public override async ValueTask ReadAsync( TextContent tc => new() { - Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }], + Contents = [new TextResourceContents { Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }], }, DataContent dc => new() { Contents = [new BlobResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = dc.MediaType, Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span) }], @@ -401,7 +401,7 @@ public override async ValueTask ReadAsync( string text => new() { - Contents = [new TextResourceContents { Uri = request.Params!.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }], + Contents = [new TextResourceContents { Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }], }, IEnumerable contents => new() @@ -416,14 +416,14 @@ public override async ValueTask ReadAsync( { TextContent tc => new TextResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = tc.Text }, DataContent dc => new BlobResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = dc.MediaType, Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span) }, @@ -436,7 +436,7 @@ public override async ValueTask ReadAsync( { Contents = strings.Select(text => new TextResourceContents { - Uri = request.Params!.Uri, + Uri = request.Params.Uri, MimeType = ProtocolResourceTemplate.MimeType, Text = text }).ToList(),