Add middleware and authz support for server-side handlers (#733)

This commit is contained in:
Stephen Halter 2025-09-10 10:07:45 -07:00 • committed by GitHub
commit d344c65120
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2871 additions and 435 deletions

View file

@ -9,7 +9,7 @@ namespace ModelContextProtocol.Protocol;
/// </summary>
/// <remarks>
/// <para>
/// When enabled, this capability allows a Model Context Protocol server to provide
/// When enabled, this capability allows a Model Context Protocol server to provide
/// auto-completion suggestions. This capability is advertised to clients during the initialize handshake.
/// </para>
/// <para>
@ -33,5 +33,5 @@ public sealed class CompletionsCapability
/// and should return appropriate completion suggestions.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>>? CompleteHandler { get; set; }
public McpRequestHandler<CompleteRequestParams, CompleteResult>? CompleteHandler { get; set; }
}

View file

@ -1,5 +1,6 @@
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
@ -29,28 +30,21 @@ public abstract class JsonRpcMessage
public string JsonRpc { get; init; } = "2.0";
/// <summary>
/// Gets or sets the transport the <see cref="JsonRpcMessage"/> was received on or should be sent over.
/// Gets or sets the contextual information for this JSON-RPC message.
/// </summary>
/// <remarks>
/// This is used to support the Streamable HTTP transport where the specification states that the server
/// SHOULD include JSON-RPC responses in the HTTP response body for the POST request containing
/// the corresponding JSON-RPC request. It may be <see langword="null"/> for other transports.
/// This property contains transport-specific and runtime context information that accompanies
/// JSON-RPC messages but is not serialized as part of the JSON-RPC payload. This includes
/// transport references, execution context, and authenticated user information.
/// </remarks>
[JsonIgnore]
public ITransport? RelatedTransport { get; set; }
/// <summary>
/// Gets or sets the <see cref="ExecutionContext"/> that should be used to run any handlers
/// </summary>
/// <remarks>
/// This is used to support the Streamable HTTP transport in its default stateful mode. In this mode,
/// the <see cref="IMcpServer"/> outlives the initial HTTP request context it was created on, and new
/// JSON-RPC messages can originate from future HTTP requests. This allows the transport to flow the
/// context with the JSON-RPC message. This is particularly useful for enabling IHttpContextAccessor
/// in tool calls.
/// This property should only be set when implementing a custom <see cref="ITransport"/>
/// that needs to pass additional per-message context or to pass a <see cref="JsonRpcMessageContext.User"/>
/// to <see cref="StreamableHttpServerTransport.HandlePostRequest(JsonRpcMessage, Stream, CancellationToken)"/>
/// or <see cref="SseResponseStreamTransport.OnMessageReceivedAsync(JsonRpcMessage, CancellationToken)"/> .
/// </remarks>
[JsonIgnore]
public ExecutionContext? ExecutionContext { get; set; }
public JsonRpcMessageContext? Context { get; set; }
/// <summary>
/// Provides a <see cref="JsonConverter"/> for <see cref="JsonRpcMessage"/> messages,

View file

@ -0,0 +1,61 @@
using ModelContextProtocol.Server;
using System.Security.Claims;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol;
/// <summary>
/// Contains contextual information for JSON-RPC messages that is not part of the JSON-RPC protocol specification.
/// </summary>
/// <remarks>
/// This class holds transport-specific and runtime context information that accompanies JSON-RPC messages
/// but is not serialized as part of the JSON-RPC payload. This includes transport references, execution context,
/// and authenticated user information.
/// </remarks>
public class JsonRpcMessageContext
{
/// <summary>
/// Gets or sets the transport the <see cref="JsonRpcMessage"/> was received on or should be sent over.
/// </summary>
/// <remarks>
/// This is used to support the Streamable HTTP transport where the specification states that the server
/// SHOULD include JSON-RPC responses in the HTTP response body for the POST request containing
/// the corresponding JSON-RPC request. It may be <see langword="null"/> for other transports.
/// </remarks>
public ITransport? RelatedTransport { get; set; }
/// <summary>
/// Gets or sets the <see cref="ExecutionContext"/> that should be used to run any handlers
/// </summary>
/// <remarks>
/// This is used to support the Streamable HTTP transport in its default stateful mode. In this mode,
/// the <see cref="IMcpServer"/> outlives the initial HTTP request context it was created on, and new
/// JSON-RPC messages can originate from future HTTP requests. This allows the transport to flow the
/// context with the JSON-RPC message. This is particularly useful for enabling IHttpContextAccessor
/// in tool calls.
/// </remarks>
public ExecutionContext? ExecutionContext { get; set; }
/// <summary>
/// Gets or sets the authenticated user associated with this JSON-RPC message.
/// </summary>
/// <remarks>
/// <para>
/// This property contains the <see cref="ClaimsPrincipal"/> representing the authenticated user
/// who initiated this JSON-RPC message. This enables request handlers to access user identity
/// and authorization information without requiring dependency on HTTP context accessors
/// or other HTTP-specific abstractions.
/// </para>
/// <para>
/// The user information is automatically populated by the transport layer when processing
/// incoming HTTP requests in ASP.NET Core scenarios. For other transport types or scenarios
/// where user authentication is not applicable, this property may be <see langword="null"/>.
/// </para>
/// <para>
/// This property is particularly useful in the Streamable HTTP transport where JSON-RPC messages
/// may outlive the original HTTP request context, allowing user identity to be preserved
/// throughout the message processing pipeline.
/// </para>
/// </remarks>
public ClaimsPrincipal? User { get; set; }
}

View file

@ -9,7 +9,7 @@ namespace ModelContextProtocol.Protocol;
/// <remarks>
/// Requests are messages that require a response from the receiver. Each request includes a unique ID
/// that will be included in the corresponding response message (either a success response or an error).
///
///
/// The receiver of a request message is expected to execute the specified method with the provided parameters
/// and return either a <see cref="JsonRpcResponse"/> with the result, or a <see cref="JsonRpcError"/>
/// if the method execution fails.
@ -36,7 +36,7 @@ public sealed class JsonRpcRequest : JsonRpcMessageWithId
Id = id,
Method = Method,
Params = Params,
RelatedTransport = RelatedTransport,
Context = Context,
};
}
}

View file

@ -18,5 +18,5 @@ public sealed class LoggingCapability
/// Gets or sets the handler for set logging level requests from clients.
/// </summary>
[JsonIgnore]
public Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SetLoggingLevelHandler { get; set; }
public McpRequestHandler<SetLevelRequestParams, EmptyResult>? SetLoggingLevelHandler { get; set; }
}

View file

@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@ -59,4 +60,10 @@ public sealed class Prompt : IBaseMetadata
/// </remarks>
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
/// <summary>
/// Gets or sets the callable server prompt corresponding to this metadata if any.
/// </summary>
[JsonIgnore]
public McpServerPrompt? McpServerPrompt { get; set; }
}

View file

@ -22,10 +22,10 @@ public sealed class PromptsCapability
/// Gets or sets whether this server supports notifications for changes to the prompt list.
/// </summary>
/// <remarks>
/// When set to <see langword="true"/>, the server will send notifications using
/// <see cref="NotificationMethods.PromptListChangedNotification"/> when prompts are added,
/// When set to <see langword="true"/>, the server will send notifications using
/// <see cref="NotificationMethods.PromptListChangedNotification"/> when prompts are added,
/// removed, or modified. Clients can register handlers for these notifications to
/// refresh their prompt cache. This capability enables clients to stay synchronized with server-side changes
/// refresh their prompt cache. This capability enables clients to stay synchronized with server-side changes
/// to available prompts.
/// </remarks>
[JsonPropertyName("listChanged")]
@ -40,15 +40,15 @@ public sealed class PromptsCapability
/// along with any prompts defined in <see cref="PromptCollection"/>.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>>? ListPromptsHandler { get; set; }
public McpRequestHandler<ListPromptsRequestParams, ListPromptsResult>? ListPromptsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.PromptsGet"/> requests.
/// </summary>
/// <remarks>
/// <para>
/// This handler is invoked when a client requests details for a specific prompt by name and provides arguments
/// for the prompt if needed. The handler receives the request context containing the prompt name and any arguments,
/// This handler is invoked when a client requests details for a specific prompt by name and provides arguments
/// for the prompt if needed. The handler receives the request context containing the prompt name and any arguments,
/// and should return a <see cref="GetPromptResult"/> with the prompt messages and other details.
/// </para>
/// <para>
@ -57,7 +57,7 @@ public sealed class PromptsCapability
/// </para>
/// </remarks>
[JsonIgnore]
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>>? GetPromptHandler { get; set; }
public McpRequestHandler<GetPromptRequestParams, GetPromptResult>? GetPromptHandler { get; set; }
/// <summary>
/// Gets or sets a collection of prompts that will be served by the server.
@ -69,7 +69,7 @@ public sealed class PromptsCapability
/// when those are provided:
/// </para>
/// <para>
/// - For <see cref="RequestMethods.PromptsList"/> requests: The server returns all prompts from this collection
/// - For <see cref="RequestMethods.PromptsList"/> requests: The server returns all prompts from this collection
/// plus any additional prompts provided by the <see cref="ListPromptsHandler"/> if it's set.
/// </para>
/// <para>

View file

@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@ -87,4 +88,10 @@ public sealed class Resource : IBaseMetadata
/// </remarks>
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; init; }
/// <summary>
/// Gets or sets the callable server resource corresponding to this metadata if any.
/// </summary>
[JsonIgnore]
public McpServerResource? McpServerResource { get; set; }
}

View file

@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@ -84,6 +85,12 @@ public sealed class ResourceTemplate : IBaseMetadata
[JsonIgnore]
public bool IsTemplated => UriTemplate.Contains('{');
/// <summary>
/// Gets or sets the callable server resource corresponding to this metadata if any.
/// </summary>
[JsonIgnore]
public McpServerResource? McpServerResource { get; set; }
/// <summary>Converts the <see cref="ResourceTemplate"/> into a <see cref="Resource"/>.</summary>
/// <returns>A <see cref="Resource"/> if <see cref="IsTemplated"/> is <see langword="false"/>; otherwise, <see langword="null"/>.</returns>
public Resource? AsResource()
@ -102,6 +109,7 @@ public sealed class ResourceTemplate : IBaseMetadata
MimeType = MimeType,
Annotations = Annotations,
Meta = Meta,
McpServerResource = McpServerResource,
};
}
}

View file

@ -21,8 +21,8 @@ public sealed class ResourcesCapability
/// Gets or sets whether this server supports notifications for changes to the resource list.
/// </summary>
/// <remarks>
/// When set to <see langword="true"/>, the server will send notifications using
/// <see cref="NotificationMethods.ResourceListChangedNotification"/> when resources are added,
/// When set to <see langword="true"/>, the server will send notifications using
/// <see cref="NotificationMethods.ResourceListChangedNotification"/> when resources are added,
/// removed, or modified. Clients can register handlers for these notifications to
/// refresh their resource cache.
/// </remarks>
@ -39,7 +39,7 @@ public sealed class ResourcesCapability
/// allowing clients to discover available resource types and their access patterns.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>>? ListResourceTemplatesHandler { get; set; }
public McpRequestHandler<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>? ListResourceTemplatesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesList"/> requests.
@ -49,7 +49,7 @@ public sealed class ResourcesCapability
/// The implementation should return a <see cref="ListResourcesResult"/> with the matching resources.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>>? ListResourcesHandler { get; set; }
public McpRequestHandler<ListResourcesRequestParams, ListResourcesResult>? ListResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesRead"/> requests.
@ -61,7 +61,7 @@ public sealed class ResourcesCapability
/// its contents in a ReadResourceResult object.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>>? ReadResourceHandler { get; set; }
public McpRequestHandler<ReadResourceRequestParams, ReadResourceResult>? ReadResourceHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesSubscribe"/> requests.
@ -74,7 +74,7 @@ public sealed class ResourcesCapability
/// requiring polling.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SubscribeToResourcesHandler { get; set; }
public McpRequestHandler<SubscribeRequestParams, EmptyResult>? SubscribeToResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesUnsubscribe"/> requests.
@ -85,7 +85,7 @@ public sealed class ResourcesCapability
/// about the specified resource.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }
public McpRequestHandler<UnsubscribeRequestParams, EmptyResult>? UnsubscribeFromResourcesHandler { get; set; }
/// <summary>
/// Gets or sets a collection of resources served by the server.

View file

@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using ModelContextProtocol.Server;
namespace ModelContextProtocol.Protocol;
@ -43,7 +44,7 @@ public sealed class Tool : IBaseMetadata
/// if an invalid schema is provided.
/// </para>
/// <para>
/// The schema typically defines the properties (parameters) that the tool accepts,
/// The schema typically defines the properties (parameters) that the tool accepts,
/// their types, and which ones are required. This helps AI models understand
/// how to structure their calls to the tool.
/// </para>
@ -52,9 +53,9 @@ public sealed class Tool : IBaseMetadata
/// </para>
/// </remarks>
[JsonPropertyName("inputSchema")]
public JsonElement InputSchema
{
get => field;
public JsonElement InputSchema
{
get => field;
set
{
if (!McpJsonUtilities.IsValidMcpToolSchema(value))
@ -114,4 +115,10 @@ public sealed class Tool : IBaseMetadata
/// </remarks>
[JsonPropertyName("_meta")]
public JsonObject? Meta { get; set; }
/// <summary>
/// Gets or sets the callable server tool corresponding to this metadata if any.
/// </summary>
[JsonIgnore]
public McpServerTool? McpServerTool { get; set; }
}

View file

@ -13,10 +13,10 @@ public sealed class ToolsCapability
/// Gets or sets whether this server supports notifications for changes to the tool list.
/// </summary>
/// <remarks>
/// When set to <see langword="true"/>, the server will send notifications using
/// <see cref="NotificationMethods.ToolListChangedNotification"/> when tools are added,
/// When set to <see langword="true"/>, the server will send notifications using
/// <see cref="NotificationMethods.ToolListChangedNotification"/> when tools are added,
/// removed, or modified. Clients can register handlers for these notifications to
/// refresh their tool cache. This capability enables clients to stay synchronized with server-side
/// refresh their tool cache. This capability enables clients to stay synchronized with server-side
/// changes to available tools.
/// </remarks>
[JsonPropertyName("listChanged")]
@ -33,19 +33,19 @@ public sealed class ToolsCapability
/// and the tools from the collection will be combined to form the complete list of available tools.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>>? ListToolsHandler { get; set; }
public McpRequestHandler<ListToolsRequestParams, ListToolsResult>? ListToolsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ToolsCall"/> requests.
/// </summary>
/// <remarks>
/// This handler is invoked when a client makes a call to a tool that isn't found in the <see cref="ToolCollection"/>.
/// The handler should implement logic to execute the requested tool and return appropriate results.
/// It receives a <see cref="RequestContext{CallToolRequestParams}"/> containing information about the tool
/// The handler should implement logic to execute the requested tool and return appropriate results.
/// It receives a <see cref="RequestContext{CallToolRequestParams}"/> containing information about the tool
/// being called and its arguments, and should return a <see cref="CallToolResult"/> with the execution results.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResult>>? CallToolHandler { get; set; }
public McpRequestHandler<CallToolRequestParams, CallToolResult>? CallToolHandler { get; set; }
/// <summary>
/// Gets or sets a collection of tools served by the server.