Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6cc05aa
Make RequestContext.Params non-nullable with new constructor; obsolet…
Copilot Mar 22, 2026
f6960a2
Fix test files for RequestContext<TParams> 3-arg constructor API change
Copilot Mar 22, 2026
db960e6
Make RequestContext.Params non-nullable
Copilot Mar 22, 2026
eec28fc
Fix NullReferenceException when request.Params is null in JSON-RPC
Copilot Mar 22, 2026
c0f7088
Revert null-conditional removal in McpServerImpl.cs; keep only Invoke…
Copilot Mar 23, 2026
a2027b5
Rename @params to parameters and add MCP9003 diagnostic for obsolete …
Copilot Mar 23, 2026
bbe96fa
Support specifying OutputSchema independently of return type for tool…
Copilot Mar 23, 2026
6586519
Bump the other-testing group with 2 updates (#1440)
dependabot[bot] Mar 23, 2026
1f54ed0
Bump danielpalme/ReportGenerator-GitHub-Action from 5.5.2 to 5.5.4 (#…
dependabot[bot] Mar 23, 2026
e170147
Update Microsoft.Extensions.AI.OpenAI version reference (#1456)
stephentoub Mar 23, 2026
70d8628
Bump Microsoft.Extensions.AI from 10.4.0 to 10.4.1 (#1461)
dependabot[bot] Mar 23, 2026
a818763
Bump Anthropic from 12.8.0 to 12.9.0 (#1460)
dependabot[bot] Mar 23, 2026
4bffdd1
Add 3-arg RequestContext constructor and obsolete 2-arg to eliminate …
Copilot Mar 23, 2026
94a3e90
Merge remote-tracking branch 'origin/main' into copilot/obsoleting-ol…
Copilot Mar 23, 2026
cf13c6b
Restore null conditional/forgiving operators on Params member access …
Copilot Mar 26, 2026
650dd37
Remove unnecessary null-forgiving operators on Params (keep only null…
Copilot Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/concepts/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt
{
var logger = context.Services?.GetService<ILogger<Program>>();

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;
Expand All @@ -339,7 +339,7 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt
catch (Exception ex)
{
var logger = context.Services?.GetService<ILogger<Program>>();
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
{
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/logging/samples/server/Tools/LoggingTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static async Task<string> LoggingTool(
int duration = 10,
int steps = 10)
{
var progressToken = context.Params?.ProgressToken;
var progressToken = context.Params.ProgressToken;
var stepDuration = duration / steps;

// <snippet_LoggingConfiguration >
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/pagination/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static async Task<string> 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++)
Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/resources/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ builder.Services.AddMcpServer()
.WithResources<MyResources>()
.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);
Expand All @@ -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 _);
}
Expand Down
1 change: 1 addition & 0 deletions docs/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TParams>(McpServer, JsonRpcRequest)` constructor is obsolete. Use the overload that accepts a `parameters` argument: `RequestContext<TParams>(McpServer, JsonRpcRequest, TParams)`. |
9 changes: 2 additions & 7 deletions samples/EverythingServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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 _);
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion samples/EverythingServer/Resources/SimpleResourceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static ResourceContents TemplateResource(RequestContext<ReadResourceReque
int index = id - 1;
if ((uint)index >= ResourceGenerator.Resources.Count)
{
throw new NotSupportedException($"Unknown resource: {requestContext.Params?.Uri}");
throw new NotSupportedException($"Unknown resource: {requestContext.Params.Uri}");
}

var resource = ResourceGenerator.Resources[index];
Expand Down
2 changes: 1 addition & 1 deletion samples/EverythingServer/Tools/LongRunningTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static async Task<string> 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++)
Expand Down
4 changes: 4 additions & 0 deletions src/Common/Obsoletions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
4 changes: 2 additions & 2 deletions src/ModelContextProtocol.Core/RequestHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest,
/// </remarks>
public void Set<TParams, TResult>(
string method,
Func<TParams?, JsonRpcRequest, CancellationToken, ValueTask<TResult>> handler,
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<TResult>> handler,
JsonTypeInfo<TParams> requestTypeInfo,
JsonTypeInfo<TResult> responseTypeInfo)
{
Expand All @@ -40,7 +40,7 @@ public void Set<TParams, TResult>(

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);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,22 +386,22 @@ public override async ValueTask<ReadResourceResult> 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)
}],
},

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<ResourceContents> contents => new()
Expand All @@ -416,14 +416,14 @@ public override async ValueTask<ReadResourceResult> 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)
},
Expand All @@ -436,7 +436,7 @@ public override async ValueTask<ReadResourceResult> ReadAsync(
{
Contents = strings.Select<string, ResourceContents>(text => new TextResourceContents
{
Uri = request.Params!.Uri,
Uri = request.Params.Uri,
MimeType = ProtocolResourceTemplate.MimeType,
Text = text
}).ToList(),
Expand Down
11 changes: 5 additions & 6 deletions src/ModelContextProtocol.Core/Server/McpServerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ private void ConfigureLogging(McpServerOptions options)
// 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.
Expand All @@ -966,28 +966,27 @@ private void ConfigureLogging(McpServerOptions options)

private ValueTask<TResult> InvokeHandlerAsync<TParams, TResult>(
McpRequestHandler<TParams, TResult> 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<TResult> InvokeScopedAsync(
McpRequestHandler<TParams, TResult> handler,
TParams? args,
TParams args,
JsonRpcRequest jsonRpcRequest,
CancellationToken cancellationToken)
{
var scope = Services?.GetService<IServiceScopeFactory>()?.CreateAsyncScope();
try
{
return await handler(
new RequestContext<TParams>(new DestinationBoundMcpServer(this, jsonRpcRequest.Context?.RelatedTransport), jsonRpcRequest)
new RequestContext<TParams>(new DestinationBoundMcpServer(this, jsonRpcRequest.Context?.RelatedTransport), jsonRpcRequest, args)
{
Services = scope?.ServiceProvider ?? Services,
Params = args
},
cancellationToken).ConfigureAwait(false);
}
Expand Down
17 changes: 16 additions & 1 deletion src/ModelContextProtocol.Core/Server/RequestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,34 @@ namespace ModelContextProtocol.Server;
/// </remarks>
public sealed class RequestContext<TParams> : MessageContext
{
/// <summary>
/// Initializes a new instance of the <see cref="RequestContext{TParams}"/> class with the specified server, JSON-RPC request, and request parameters.
/// </summary>
/// <param name="server">The server with which this instance is associated.</param>
/// <param name="jsonRpcRequest">The JSON-RPC request associated with this context.</param>
/// <param name="parameters">The parameters associated with this request.</param>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="jsonRpcRequest"/> is <see langword="null"/>.</exception>
public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams parameters)
: base(server, jsonRpcRequest)
{
Params = parameters;
}

/// <summary>
/// Initializes a new instance of the <see cref="RequestContext{TParams}"/> class with the specified server and JSON-RPC request.
/// </summary>
/// <param name="server">The server with which this instance is associated.</param>
/// <param name="jsonRpcRequest">The JSON-RPC request associated with this context.</param>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="jsonRpcRequest"/> is <see langword="null"/>.</exception>
[Obsolete(Obsoletions.RequestContextParamsConstructor_Message, DiagnosticId = Obsoletions.RequestContextParamsConstructor_DiagnosticId, UrlFormat = Obsoletions.RequestContextParamsConstructor_Url)]
public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest)
: base(server, jsonRpcRequest)
{
Params = default!;
}

/// <summary>Gets or sets the parameters associated with this request.</summary>
public TParams? Params { get; set; }
public TParams Params { get; set; }

/// <summary>
/// Gets or sets the primitive that matched the request.
Expand Down
11 changes: 3 additions & 8 deletions tests/ModelContextProtocol.ConformanceServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
Expand All @@ -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 _);
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public static async Task<string> ToolWithProgress(
RequestContext<CallToolRequestParams> context,
CancellationToken cancellationToken)
{
var progressToken = context.Params?.ProgressToken;
var progressToken = context.Params.ProgressToken;

if (progressToken is not null)
{
Expand Down
Loading
Loading