Skip to content

Commit c0f7088

Browse files
Copilothalter73
andcommitted
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
1 parent eec28fc commit c0f7088

2 files changed

Lines changed: 39 additions & 41 deletions

File tree

src/ModelContextProtocol.Core/RequestHandlers.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ namespace ModelContextProtocol;
77

88
internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest, CancellationToken, Task<JsonNode?>>>
99
{
10-
private static readonly JsonNode EmptyJsonObject = JsonNode.Parse("{}")!;
11-
1210
/// <summary>
1311
/// Registers a handler for incoming requests of a specific method in the MCP protocol.
1412
/// </summary>
@@ -42,10 +40,7 @@ public void Set<TParams, TResult>(
4240

4341
this[method] = async (request, cancellationToken) =>
4442
{
45-
// When request.Params is null (e.g. the client omitted "params" from the JSON-RPC message),
46-
// deserialize from an empty JSON object so we get a valid default TParams instance
47-
// rather than null, since RequestContext.Params is now non-nullable.
48-
TParams typedRequest = JsonSerializer.Deserialize(request.Params ?? EmptyJsonObject, requestTypeInfo)!;
43+
TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!;
4944
object? result = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false);
5045
return JsonSerializer.SerializeToNode(result, responseTypeInfo);
5146
};

src/ModelContextProtocol.Core/Server/McpServerImpl.cs

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ private void ConfigureInitialize(McpServerOptions options)
208208
_requestHandlers.Set(RequestMethods.Initialize,
209209
async (request, _, _) =>
210210
{
211-
_clientCapabilities = request.Capabilities ?? new();
212-
_clientInfo = request.ClientInfo;
211+
_clientCapabilities = request?.Capabilities ?? new();
212+
_clientInfo = request?.ClientInfo;
213213

214214
// Use the ClientInfo to update the session EndpointName for logging.
215215
UpdateEndpointNameWithClientInfo();
@@ -219,7 +219,7 @@ private void ConfigureInitialize(McpServerOptions options)
219219
// Otherwise, try to use whatever the client requested as long as it's supported.
220220
// If it's not supported, fall back to the latest supported version.
221221
string? protocolVersion = options.ProtocolVersion;
222-
protocolVersion ??= request.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
222+
protocolVersion ??= request?.ProtocolVersion is string clientProtocolVersion && McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
223223
clientProtocolVersion :
224224
McpSessionHandler.LatestProtocolVersion;
225225

@@ -266,7 +266,7 @@ private void ConfigureCompletion(McpServerOptions options)
266266
CompleteResult result = await originalCompleteHandler(request, cancellationToken).ConfigureAwait(false);
267267

268268
string[]? allowedValues = null;
269-
switch (request.Params.Ref)
269+
switch (request.Params?.Ref)
270270
{
271271
case PromptReference pr when promptCompletions is not null:
272272
if (promptCompletions.TryGetValue(pr.Name, out var promptParams))
@@ -285,7 +285,7 @@ private void ConfigureCompletion(McpServerOptions options)
285285

286286
if (allowedValues is not null)
287287
{
288-
string partialValue = request.Params.Argument.Value;
288+
string partialValue = request.Params!.Argument.Value;
289289
foreach (var v in allowedValues)
290290
{
291291
if (v.StartsWith(partialValue, StringComparison.OrdinalIgnoreCase))
@@ -409,7 +409,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null &&
409409

410410
listResourcesHandler ??= (static async (_, __) => new ListResourcesResult());
411411
listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult());
412-
readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params.Uri}'", McpErrorCode.ResourceNotFound));
412+
readResourceHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.ResourceNotFound));
413413
subscribeHandler ??= (static async (_, __) => new EmptyResult());
414414
unsubscribeHandler ??= (static async (_, __) => new EmptyResult());
415415
var listChanged = resourcesCapability?.ListChanged;
@@ -425,7 +425,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null &&
425425
await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(false) :
426426
new();
427427

428-
if (request.Params.Cursor is null)
428+
if (request.Params?.Cursor is null)
429429
{
430430
foreach (var r in resources)
431431
{
@@ -446,7 +446,7 @@ await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(fa
446446
await originalListResourceTemplatesHandler(request, cancellationToken).ConfigureAwait(false) :
447447
new();
448448

449-
if (request.Params.Cursor is null)
449+
if (request.Params?.Cursor is null)
450450
{
451451
foreach (var rt in resources)
452452
{
@@ -484,7 +484,7 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure
484484
async (request, cancellationToken) =>
485485
{
486486
// Initial handler that sets MatchedPrimitive
487-
if (request.Params.Uri is { } uri && resources is not null)
487+
if (request.Params?.Uri is { } uri && resources is not null)
488488
{
489489
// First try an O(1) lookup by exact match.
490490
if (resources.TryGetPrimitive(uri, out var resource) && !resource.IsTemplated)
@@ -508,12 +508,12 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure
508508
try
509509
{
510510
var result = await handler(request, cancellationToken).ConfigureAwait(false);
511-
ReadResourceCompleted(request.Params.Uri ?? string.Empty);
511+
ReadResourceCompleted(request.Params?.Uri ?? string.Empty);
512512
return result;
513513
}
514514
catch (Exception e)
515515
{
516-
ReadResourceError(request.Params.Uri ?? string.Empty, e);
516+
ReadResourceError(request.Params?.Uri ?? string.Empty, e);
517517
throw;
518518
}
519519
});
@@ -570,7 +570,7 @@ private void ConfigurePrompts(McpServerOptions options)
570570
ServerCapabilities.Prompts = new();
571571

572572
listPromptsHandler ??= (static async (_, __) => new ListPromptsResult());
573-
getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params.Name}'", McpErrorCode.InvalidParams));
573+
getPromptHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
574574
var listChanged = promptsCapability?.ListChanged;
575575

576576
// Handle tools provided via DI by augmenting the handlers to incorporate them.
@@ -583,7 +583,7 @@ private void ConfigurePrompts(McpServerOptions options)
583583
await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(false) :
584584
new();
585585

586-
if (request.Params.Cursor is null)
586+
if (request.Params?.Cursor is null)
587587
{
588588
foreach (var p in prompts)
589589
{
@@ -613,7 +613,7 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals
613613
async (request, cancellationToken) =>
614614
{
615615
// Initial handler that sets MatchedPrimitive
616-
if (request.Params.Name is { } promptName && prompts is not null &&
616+
if (request.Params?.Name is { } promptName && prompts is not null &&
617617
prompts.TryGetPrimitive(promptName, out var prompt))
618618
{
619619
request.MatchedPrimitive = prompt;
@@ -622,12 +622,12 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals
622622
try
623623
{
624624
var result = await handler(request, cancellationToken).ConfigureAwait(false);
625-
GetPromptCompleted(request.Params.Name ?? string.Empty);
625+
GetPromptCompleted(request.Params?.Name ?? string.Empty);
626626
return result;
627627
}
628628
catch (Exception e)
629629
{
630-
GetPromptError(request.Params.Name ?? string.Empty, e);
630+
GetPromptError(request.Params?.Name ?? string.Empty, e);
631631
throw;
632632
}
633633
});
@@ -663,7 +663,7 @@ private void ConfigureTools(McpServerOptions options)
663663
ServerCapabilities.Tools = new();
664664

665665
listToolsHandler ??= (static async (_, __) => new ListToolsResult());
666-
callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams));
666+
callToolHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
667667
var listChanged = toolsCapability?.ListChanged;
668668

669669
// Handle tools provided via DI by augmenting the handlers to incorporate them.
@@ -676,7 +676,7 @@ private void ConfigureTools(McpServerOptions options)
676676
await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) :
677677
new();
678678

679-
if (request.Params.Cursor is null)
679+
if (request.Params?.Cursor is null)
680680
{
681681
foreach (var t in tools)
682682
{
@@ -697,7 +697,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
697697
var taskSupport = tool.ProtocolTool.Execution?.TaskSupport ?? ToolTaskSupport.Forbidden;
698698

699699
// Check if this is a task-augmented request
700-
if (request.Params.Task is { } taskMetadata)
700+
if (request.Params?.Task is { } taskMetadata)
701701
{
702702
// Validate tool-level task support
703703
if (taskSupport is ToolTaskSupport.Forbidden)
@@ -735,7 +735,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
735735
async (request, cancellationToken) =>
736736
{
737737
// Initial handler that sets MatchedPrimitive
738-
if (request.Params.Name is { } toolName && tools is not null &&
738+
if (request.Params?.Name is { } toolName && tools is not null &&
739739
tools.TryGetPrimitive(toolName, out var tool))
740740
{
741741
request.MatchedPrimitive = tool;
@@ -749,14 +749,14 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
749749
// in ExecuteToolAsTaskAsync when the tool actually completes.
750750
if (result.Task is null)
751751
{
752-
ToolCallCompleted(request.Params.Name ?? string.Empty, result.IsError is true);
752+
ToolCallCompleted(request.Params?.Name ?? string.Empty, result.IsError is true);
753753
}
754754

755755
return result;
756756
}
757757
catch (Exception e)
758758
{
759-
ToolCallError(request.Params.Name ?? string.Empty, e);
759+
ToolCallError(request.Params?.Name ?? string.Empty, e);
760760

761761
if ((e is OperationCanceledException && cancellationToken.IsCancellationRequested) || e is McpProtocolException)
762762
{
@@ -769,8 +769,8 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
769769
Content = [new TextContentBlock
770770
{
771771
Text = e is McpException ?
772-
$"An error occurred invoking '{request.Params.Name}': {e.Message}" :
773-
$"An error occurred invoking '{request.Params.Name}'.",
772+
$"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
773+
$"An error occurred invoking '{request.Params?.Name}'.",
774774
}],
775775
};
776776
}
@@ -818,7 +818,7 @@ private void ConfigureTasks(McpServerOptions options)
818818
// tasks/get handler - Retrieve task status
819819
McpRequestHandler<GetTaskRequestParams, McpTask> getTaskHandler = async (request, cancellationToken) =>
820820
{
821-
if (request.Params.TaskId is not { } taskId)
821+
if (request.Params?.TaskId is not { } taskId)
822822
{
823823
throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams);
824824
}
@@ -839,7 +839,7 @@ private void ConfigureTasks(McpServerOptions options)
839839

840840
async Task<JsonElement> GetTaskResultAsync(RequestContext<GetTaskPayloadRequestParams> request, CancellationToken cancellationToken)
841841
{
842-
if (request.Params.TaskId is not { } taskId)
842+
if (request.Params?.TaskId is not { } taskId)
843843
{
844844
throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams);
845845
}
@@ -872,14 +872,14 @@ async Task<JsonElement> GetTaskResultAsync(RequestContext<GetTaskPayloadRequestP
872872
// tasks/list handler - List tasks with pagination
873873
McpRequestHandler<ListTasksRequestParams, ListTasksResult> listTasksHandler = async (request, cancellationToken) =>
874874
{
875-
var cursor = request.Params.Cursor;
875+
var cursor = request.Params?.Cursor;
876876
return await taskStore.ListTasksAsync(cursor, SessionId, cancellationToken).ConfigureAwait(false);
877877
};
878878

879879
// tasks/cancel handler - Cancel a task
880880
McpRequestHandler<CancelMcpTaskRequestParams, McpTask> cancelTaskHandler = async (request, cancellationToken) =>
881881
{
882-
if (request.Params.TaskId is not { } taskId)
882+
if (request.Params?.TaskId is not { } taskId)
883883
{
884884
throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams);
885885
}
@@ -941,17 +941,20 @@ private void ConfigureLogging(McpServerOptions options)
941941
(request, jsonRpcRequest, cancellationToken) =>
942942
{
943943
// Store the provided level.
944-
if (_loggingLevel is null)
944+
if (request is not null)
945945
{
946-
Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null);
947-
}
946+
if (_loggingLevel is null)
947+
{
948+
Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null);
949+
}
948950

949-
_loggingLevel.Value = request.Level;
951+
_loggingLevel.Value = request.Level;
952+
}
950953

951954
// If a handler was provided, now delegate to it.
952955
if (setLoggingLevelHandler is not null)
953956
{
954-
return InvokeHandlerAsync(setLoggingLevelHandler, request, jsonRpcRequest, cancellationToken);
957+
return InvokeHandlerAsync(setLoggingLevelHandler, request!, jsonRpcRequest, cancellationToken);
955958
}
956959

957960
// Otherwise, consider it handled.
@@ -1163,7 +1166,7 @@ private async ValueTask<CallToolResult> ExecuteToolAsTaskAsync(
11631166

11641167
// Invoke the tool with task-specific cancellation token
11651168
var result = await tool.InvokeAsync(request, taskCancellationToken).ConfigureAwait(false);
1166-
ToolCallCompleted(request.Params.Name ?? string.Empty, result.IsError is true);
1169+
ToolCallCompleted(request.Params?.Name ?? string.Empty, result.IsError is true);
11671170

11681171
// Determine final status based on whether there was an error
11691172
var finalStatus = result.IsError is true ? McpTaskStatus.Failed : McpTaskStatus.Completed;
@@ -1192,7 +1195,7 @@ private async ValueTask<CallToolResult> ExecuteToolAsTaskAsync(
11921195
catch (Exception ex)
11931196
{
11941197
// Log the error
1195-
ToolCallError(request.Params.Name ?? string.Empty, ex);
1198+
ToolCallError(request.Params?.Name ?? string.Empty, ex);
11961199

11971200
// Store error result
11981201
var errorResult = new CallToolResult

0 commit comments

Comments
 (0)