Add 3-arg RequestContext constructor and obsolete 2-arg to eliminate null-forgiving operator usage (#1462)
This commit is contained in:
parent
afc40d1e2e
commit
7dcdbafe9d
33 changed files with 190 additions and 219 deletions
|
|
@ -317,7 +317,7 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
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);
|
var result = await next(context, cancellationToken);
|
||||||
logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools");
|
logger?.LogInformation($"Returning {result.Tools?.Count ?? 0} tools");
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -339,7 +339,7 @@ Execution flow: `filter1 -> filter2 -> filter3 -> baseHandler -> filter3 -> filt
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var logger = context.Services?.GetService<ILogger<Program>>();
|
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
|
return new CallToolResult
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public class LoggingTools
|
||||||
int duration = 10,
|
int duration = 10,
|
||||||
int steps = 10)
|
int steps = 10)
|
||||||
{
|
{
|
||||||
var progressToken = context.Params?.ProgressToken;
|
var progressToken = context.Params.ProgressToken;
|
||||||
var stepDuration = duration / steps;
|
var stepDuration = duration / steps;
|
||||||
|
|
||||||
// <snippet_LoggingConfiguration >
|
// <snippet_LoggingConfiguration >
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ builder.Services.AddMcpServer()
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
|
|
||||||
// Parse cursor to determine starting position
|
// Parse cursor to determine starting position
|
||||||
if (ctx.Params?.Cursor is { } cursor)
|
if (ctx.Params.Cursor is { } cursor)
|
||||||
{
|
{
|
||||||
startIndex = int.Parse(cursor);
|
startIndex = int.Parse(cursor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public class LongRunningTools
|
||||||
int duration = 10,
|
int duration = 10,
|
||||||
int steps = 5)
|
int steps = 5)
|
||||||
{
|
{
|
||||||
var progressToken = context.Params?.ProgressToken;
|
var progressToken = context.Params.ProgressToken;
|
||||||
var stepDuration = duration / steps;
|
var stepDuration = duration / steps;
|
||||||
|
|
||||||
for (int i = 1; i <= steps; i++)
|
for (int i = 1; i <= steps; i++)
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ builder.Services.AddMcpServer()
|
||||||
.WithResources<MyResources>()
|
.WithResources<MyResources>()
|
||||||
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
|
.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)
|
// Track the subscription (e.g., in a concurrent dictionary)
|
||||||
subscriptions[ctx.Server.SessionId].TryAdd(uri, 0);
|
subscriptions[ctx.Server.SessionId].TryAdd(uri, 0);
|
||||||
|
|
@ -221,7 +221,7 @@ builder.Services.AddMcpServer()
|
||||||
})
|
})
|
||||||
.WithUnsubscribeFromResourcesHandler(async (ctx, ct) =>
|
.WithUnsubscribeFromResourcesHandler(async (ctx, ct) =>
|
||||||
{
|
{
|
||||||
if (ctx.Params?.Uri is { } uri)
|
if (ctx.Params.Uri is { } uri)
|
||||||
{
|
{
|
||||||
subscriptions[ctx.Server.SessionId].TryRemove(uri, out _);
|
subscriptions[ctx.Server.SessionId].TryRemove(uri, out _);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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. |
|
| `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()`. |
|
| `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)`. |
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ builder.Services
|
||||||
{
|
{
|
||||||
throw new McpException("Cannot add subscription for server with null SessionId");
|
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);
|
subscriptions[ctx.Server.SessionId].TryAdd(uri, 0);
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ builder.Services
|
||||||
{
|
{
|
||||||
throw new McpException("Cannot remove subscription for server with null SessionId");
|
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 _);
|
subscriptions[ctx.Server.SessionId].TryRemove(uri, out _);
|
||||||
}
|
}
|
||||||
|
|
@ -212,11 +212,6 @@ builder.Services
|
||||||
})
|
})
|
||||||
.WithSetLoggingLevelHandler(async (ctx, ct) =>
|
.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
|
// The SDK updates the LoggingLevel field of the IMcpServer
|
||||||
|
|
||||||
await ctx.Server.SendNotificationAsync("notifications/message", new
|
await ctx.Server.SendNotificationAsync("notifications/message", new
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public class SimpleResourceType
|
||||||
int index = id - 1;
|
int index = id - 1;
|
||||||
if ((uint)index >= ResourceGenerator.Resources.Count)
|
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];
|
var resource = ResourceGenerator.Resources[index];
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public class LongRunningTool
|
||||||
int duration = 10,
|
int duration = 10,
|
||||||
int steps = 5)
|
int steps = 5)
|
||||||
{
|
{
|
||||||
var progressToken = context.Params?.ProgressToken;
|
var progressToken = context.Params.ProgressToken;
|
||||||
var stepDuration = duration / steps;
|
var stepDuration = duration / steps;
|
||||||
|
|
||||||
for (int i = 1; i <= steps + 1; i++)
|
for (int i = 1; i <= steps + 1; i++)
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,8 @@ internal static class Obsoletions
|
||||||
|
|
||||||
// MCP9002 was used for the AddXxxFilter extension methods on IMcpServerBuilder that were superseded by
|
// 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.
|
// 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";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest,
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void Set<TParams, TResult>(
|
public void Set<TParams, TResult>(
|
||||||
string method,
|
string method,
|
||||||
Func<TParams?, JsonRpcRequest, CancellationToken, ValueTask<TResult>> handler,
|
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<TResult>> handler,
|
||||||
JsonTypeInfo<TParams> requestTypeInfo,
|
JsonTypeInfo<TParams> requestTypeInfo,
|
||||||
JsonTypeInfo<TResult> responseTypeInfo)
|
JsonTypeInfo<TResult> responseTypeInfo)
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +40,7 @@ internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest,
|
||||||
|
|
||||||
this[method] = async (request, cancellationToken) =>
|
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);
|
object? result = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false);
|
||||||
return JsonSerializer.SerializeToNode(result, responseTypeInfo);
|
return JsonSerializer.SerializeToNode(result, responseTypeInfo);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -386,14 +386,14 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
|
|
||||||
TextContent tc => new()
|
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()
|
DataContent dc => new()
|
||||||
{
|
{
|
||||||
Contents = [new BlobResourceContents
|
Contents = [new BlobResourceContents
|
||||||
{
|
{
|
||||||
Uri = request.Params!.Uri,
|
Uri = request.Params.Uri,
|
||||||
MimeType = dc.MediaType,
|
MimeType = dc.MediaType,
|
||||||
Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span)
|
Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span)
|
||||||
}],
|
}],
|
||||||
|
|
@ -401,7 +401,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
|
|
||||||
string text => new()
|
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()
|
IEnumerable<ResourceContents> contents => new()
|
||||||
|
|
@ -416,14 +416,14 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
{
|
{
|
||||||
TextContent tc => new TextResourceContents
|
TextContent tc => new TextResourceContents
|
||||||
{
|
{
|
||||||
Uri = request.Params!.Uri,
|
Uri = request.Params.Uri,
|
||||||
MimeType = ProtocolResourceTemplate.MimeType,
|
MimeType = ProtocolResourceTemplate.MimeType,
|
||||||
Text = tc.Text
|
Text = tc.Text
|
||||||
},
|
},
|
||||||
|
|
||||||
DataContent dc => new BlobResourceContents
|
DataContent dc => new BlobResourceContents
|
||||||
{
|
{
|
||||||
Uri = request.Params!.Uri,
|
Uri = request.Params.Uri,
|
||||||
MimeType = dc.MediaType,
|
MimeType = dc.MediaType,
|
||||||
Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span)
|
Blob = EncodingUtilities.GetUtf8Bytes(dc.Base64Data.Span)
|
||||||
},
|
},
|
||||||
|
|
@ -436,7 +436,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
{
|
{
|
||||||
Contents = strings.Select<string, ResourceContents>(text => new TextResourceContents
|
Contents = strings.Select<string, ResourceContents>(text => new TextResourceContents
|
||||||
{
|
{
|
||||||
Uri = request.Params!.Uri,
|
Uri = request.Params.Uri,
|
||||||
MimeType = ProtocolResourceTemplate.MimeType,
|
MimeType = ProtocolResourceTemplate.MimeType,
|
||||||
Text = text
|
Text = text
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
|
|
|
||||||
|
|
@ -954,7 +954,7 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
// If a handler was provided, now delegate to it.
|
// If a handler was provided, now delegate to it.
|
||||||
if (setLoggingLevelHandler is not null)
|
if (setLoggingLevelHandler is not null)
|
||||||
{
|
{
|
||||||
return InvokeHandlerAsync(setLoggingLevelHandler, request, jsonRpcRequest, cancellationToken);
|
return InvokeHandlerAsync(setLoggingLevelHandler, request!, jsonRpcRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, consider it handled.
|
// Otherwise, consider it handled.
|
||||||
|
|
@ -966,17 +966,17 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
|
|
||||||
private ValueTask<TResult> InvokeHandlerAsync<TParams, TResult>(
|
private ValueTask<TResult> InvokeHandlerAsync<TParams, TResult>(
|
||||||
McpRequestHandler<TParams, TResult> handler,
|
McpRequestHandler<TParams, TResult> handler,
|
||||||
TParams? args,
|
TParams args,
|
||||||
JsonRpcRequest jsonRpcRequest,
|
JsonRpcRequest jsonRpcRequest,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return _servicesScopePerRequest ?
|
return _servicesScopePerRequest ?
|
||||||
InvokeScopedAsync(handler, args, jsonRpcRequest, cancellationToken) :
|
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(
|
async ValueTask<TResult> InvokeScopedAsync(
|
||||||
McpRequestHandler<TParams, TResult> handler,
|
McpRequestHandler<TParams, TResult> handler,
|
||||||
TParams? args,
|
TParams args,
|
||||||
JsonRpcRequest jsonRpcRequest,
|
JsonRpcRequest jsonRpcRequest,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
@ -984,10 +984,9 @@ internal sealed partial class McpServerImpl : McpServer
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await handler(
|
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,
|
Services = scope?.ServiceProvider ?? Services,
|
||||||
Params = args
|
|
||||||
},
|
},
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,34 @@ namespace ModelContextProtocol.Server;
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class RequestContext<TParams> : MessageContext
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RequestContext{TParams}"/> class with the specified server and JSON-RPC request.
|
/// Initializes a new instance of the <see cref="RequestContext{TParams}"/> class with the specified server and JSON-RPC request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="server">The server with which this instance is associated.</param>
|
/// <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="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>
|
/// <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)
|
public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest)
|
||||||
: base(server, jsonRpcRequest)
|
: base(server, jsonRpcRequest)
|
||||||
{
|
{
|
||||||
|
Params = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets or sets the parameters associated with this request.</summary>
|
/// <summary>Gets or sets the parameters associated with this request.</summary>
|
||||||
public TParams? Params { get; set; }
|
public TParams Params { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the primitive that matched the request.
|
/// Gets or sets the primitive that matched the request.
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public class Program
|
||||||
// For the test_reconnection tool, enable polling mode after the tool runs.
|
// For the test_reconnection tool, enable polling mode after the tool runs.
|
||||||
// This stores the result and closes the SSE stream, so the client
|
// This stores the result and closes the SSE stream, so the client
|
||||||
// must reconnect via GET with Last-Event-ID to retrieve the result.
|
// 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);
|
await request.EnablePollingAsync(TimeSpan.FromMilliseconds(500), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ public class Program
|
||||||
{
|
{
|
||||||
throw new McpException("Cannot add subscription for server with null SessionId");
|
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());
|
var sessionSubscriptions = subscriptions.GetOrAdd(ctx.Server.SessionId, _ => new());
|
||||||
sessionSubscriptions.TryAdd(uri, 0);
|
sessionSubscriptions.TryAdd(uri, 0);
|
||||||
|
|
@ -68,7 +68,7 @@ public class Program
|
||||||
{
|
{
|
||||||
throw new McpException("Cannot remove subscription for server with null SessionId");
|
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 _);
|
subscriptions[ctx.Server.SessionId].TryRemove(uri, out _);
|
||||||
}
|
}
|
||||||
|
|
@ -91,11 +91,6 @@ public class Program
|
||||||
})
|
})
|
||||||
.WithSetLoggingLevelHandler(async (ctx, ct) =>
|
.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
|
// The SDK updates the LoggingLevel field of the McpServer
|
||||||
// Send a log notification to confirm the level was set
|
// Send a log notification to confirm the level was set
|
||||||
await ctx.Server.SendNotificationAsync("notifications/message", new LoggingMessageNotificationParams
|
await ctx.Server.SendNotificationAsync("notifications/message", new LoggingMessageNotificationParams
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ public class ConformanceTools
|
||||||
RequestContext<CallToolRequestParams> context,
|
RequestContext<CallToolRequestParams> context,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var progressToken = context.Params?.ProgressToken;
|
var progressToken = context.Params.ProgressToken;
|
||||||
|
|
||||||
if (progressToken is not null)
|
if (progressToken is not null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -204,9 +204,9 @@ internal static class Program
|
||||||
};
|
};
|
||||||
options.Handlers.CallToolHandler = async (request, cancellationToken) =>
|
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);
|
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
@ -215,16 +215,16 @@ internal static class Program
|
||||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (request.Params?.Name == "echoSessionId")
|
else if (request.Params.Name == "echoSessionId")
|
||||||
{
|
{
|
||||||
return new CallToolResult
|
return new CallToolResult
|
||||||
{
|
{
|
||||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
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("prompt", out var prompt) ||
|
||||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||||
{
|
{
|
||||||
|
|
@ -238,16 +238,16 @@ internal static class Program
|
||||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {sampleResult.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text}" }]
|
Content = [new TextContentBlock { Text = $"LLM sampling result: {sampleResult.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text}" }]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (request.Params?.Name == "echoCliArg")
|
else if (request.Params.Name == "echoCliArg")
|
||||||
{
|
{
|
||||||
return new CallToolResult
|
return new CallToolResult
|
||||||
{
|
{
|
||||||
Content = [new TextContentBlock { Text = cliArg ?? "null" }]
|
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);
|
throw new McpProtocolException("Missing required argument 'durationMs'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
@ -258,9 +258,9 @@ internal static class Program
|
||||||
Content = [new TextContentBlock { Text = $"Long-running operation completed after {durationMs}ms" }]
|
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);
|
throw new McpProtocolException("Missing required argument 'exitCode'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +271,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
else
|
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 @@ internal static class Program
|
||||||
options.Handlers.GetPromptHandler = async (request, cancellationToken) =>
|
options.Handlers.GetPromptHandler = async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
List<PromptMessage> messages = [];
|
List<PromptMessage> messages = [];
|
||||||
if (request.Params?.Name == "simple-prompt")
|
if (request.Params.Name == "simple-prompt")
|
||||||
{
|
{
|
||||||
messages.Add(new PromptMessage
|
messages.Add(new PromptMessage
|
||||||
{
|
{
|
||||||
|
|
@ -343,7 +343,7 @@ internal static class Program
|
||||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
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 city = request.Params.Arguments?["city"].ToString() ?? "unknown";
|
||||||
string state = request.Params.Arguments?["state"].ToString() ?? "";
|
string state = request.Params.Arguments?["state"].ToString() ?? "";
|
||||||
|
|
@ -354,7 +354,7 @@ internal static class Program
|
||||||
Content = new TextContentBlock { Text = $"What's weather in {location}?" },
|
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 department = request.Params.Arguments?["department"].ToString() ?? "unknown";
|
||||||
string name = request.Params.Arguments?["name"].ToString() ?? "unknown";
|
string name = request.Params.Arguments?["name"].ToString() ?? "unknown";
|
||||||
|
|
@ -366,7 +366,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new McpProtocolException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
throw new McpProtocolException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GetPromptResult
|
return new GetPromptResult
|
||||||
|
|
@ -382,11 +382,6 @@ internal static class Program
|
||||||
{
|
{
|
||||||
options.Handlers.SetLoggingLevelHandler = async (request, cancellationToken) =>
|
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;
|
_minimumLoggingLevel = request.Params.Level;
|
||||||
|
|
||||||
return new EmptyResult();
|
return new EmptyResult();
|
||||||
|
|
@ -452,7 +447,7 @@ internal static class Program
|
||||||
options.Handlers.ListResourcesHandler = async (request, cancellationToken) =>
|
options.Handlers.ListResourcesHandler = async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
if (request.Params?.Cursor is not null)
|
if (request.Params.Cursor is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -481,7 +476,7 @@ internal static class Program
|
||||||
|
|
||||||
options.Handlers.ReadResourceHandler = async (request, cancellationToken) =>
|
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);
|
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
@ -518,7 +513,7 @@ internal static class Program
|
||||||
|
|
||||||
options.Handlers.SubscribeToResourcesHandler = async (request, cancellationToken) =>
|
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);
|
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
@ -535,7 +530,7 @@ internal static class Program
|
||||||
|
|
||||||
options.Handlers.UnsubscribeFromResourcesHandler = async (request, cancellationToken) =>
|
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);
|
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
@ -563,7 +558,7 @@ internal static class Program
|
||||||
options.Handlers.CompleteHandler = async (request, cancellationToken) =>
|
options.Handlers.CompleteHandler = async (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
string[]? values;
|
string[]? values;
|
||||||
switch (request.Params?.Ref)
|
switch (request.Params.Ref)
|
||||||
{
|
{
|
||||||
case ResourceTemplateReference rtr:
|
case ResourceTemplateReference rtr:
|
||||||
var resourceId = rtr.Uri?.Split('/').LastOrDefault();
|
var resourceId = rtr.Uri?.Split('/').LastOrDefault();
|
||||||
|
|
@ -571,7 +566,7 @@ internal static class Program
|
||||||
return new CompleteResult { Completion = new() { Values = [] } };
|
return new CompleteResult { Completion = new() { Values = [] } };
|
||||||
|
|
||||||
// Filter resource IDs that start with the input value
|
// 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 } };
|
return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||||
|
|
||||||
case PromptReference pr:
|
case PromptReference pr:
|
||||||
|
|
@ -583,7 +578,7 @@ internal static class Program
|
||||||
return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||||
|
|
||||||
default:
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ public class Program
|
||||||
|
|
||||||
ReadResourceHandler = async (request, cancellationToken) =>
|
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);
|
throw new McpProtocolException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public class McpClientMetaTests : ClientServerTestBase
|
||||||
async (RequestContext<CallToolRequestParams> context) =>
|
async (RequestContext<CallToolRequestParams> context) =>
|
||||||
{
|
{
|
||||||
// Access the foo property of _meta field from the request parameters
|
// 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 that the meta foo is correctly passed
|
||||||
Assert.NotNull(metaFoo);
|
Assert.NotNull(metaFoo);
|
||||||
|
|
@ -73,7 +73,7 @@ public class McpClientMetaTests : ClientServerTestBase
|
||||||
(RequestContext<ReadResourceRequestParams> context) =>
|
(RequestContext<ReadResourceRequestParams> context) =>
|
||||||
{
|
{
|
||||||
// Access the foo property of _meta field from the request parameters
|
// 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 that the meta foo is correctly passed
|
||||||
Assert.NotNull(metaFoo);
|
Assert.NotNull(metaFoo);
|
||||||
|
|
@ -109,7 +109,7 @@ public class McpClientMetaTests : ClientServerTestBase
|
||||||
(RequestContext<GetPromptRequestParams> context) =>
|
(RequestContext<GetPromptRequestParams> context) =>
|
||||||
{
|
{
|
||||||
// Access the foo property of _meta field from the request parameters
|
// 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 that the meta foo is correctly passed
|
||||||
Assert.NotNull(metaFoo);
|
Assert.NotNull(metaFoo);
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public class McpClientPromptTests : ClientServerTestBase
|
||||||
|
|
||||||
[McpServerPrompt, Description("Echoes back the metadata it receives")]
|
[McpServerPrompt, Description("Echoes back the metadata it receives")]
|
||||||
public static ChatMessage MetadataEcho(RequestContext<GetPromptRequestParams> context) =>
|
public static ChatMessage MetadataEcho(RequestContext<GetPromptRequestParams> context) =>
|
||||||
new(ChatRole.User, context.Params?.Meta?.ToJsonString() ?? "{}");
|
new(ChatRole.User, context.Params.Meta?.ToJsonString() ?? "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue