-
Notifications
You must be signed in to change notification settings - Fork 238
Add StreamableHttpHandler and WithHttpTransport #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
stephentoub
merged 13 commits into
modelcontextprotocol:main
from
halter73:with-http-transport
Apr 14, 2025
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
318fcaf
Simplify ModelContextProtocol.AspNetCore README
halter73 cad85a6
Add StreamableHttpHandler
halter73 3b13429
Use "docker info" in CheckIsDockerAvailable
halter73 6b45f35
Call WithHttpTransport in samples, tests and README
halter73 82813d2
Cleanup test namespaces
halter73 65a82ec
Add CanConnect_WithMcpClient_AfterCustomizingRoute test
halter73 5de8bc9
Simplify relative URI handling
halter73 37a1d4e
Handle request made directly to the MapMcp route pattern
halter73 f47ad26
Add Messages_FromNewUser_AreRejected test
halter73 b5a1bfd
Fix README
halter73 cc2a4f1
Shorten UserIdClaim ValueTuple names
halter73 3167bdc
Merge remote-tracking branch 'origin/main' into with-http-transport
halter73 d9e737c
Remove MaxReconnectAttempts and ReconnectDelay from SseClientTranspor…
halter73 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using ModelContextProtocol.AspNetCore; | ||
using ModelContextProtocol.Server; | ||
|
||
namespace Microsoft.Extensions.DependencyInjection; | ||
|
||
/// <summary> | ||
/// Provides methods for configuring HTTP MCP servers via dependency injection. | ||
/// </summary> | ||
public static class HttpMcpServerBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Adds the services necessary for <see cref="M:McpEndpointRouteBuilderExtensions.MapMcp"/> | ||
/// to handle MCP requests and sessions using the MCP HTTP Streaming transport. For more information on configuring the underlying HTTP server | ||
/// to control things like port binding custom TLS certificates, see the <see href="https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis">Minimal APIs quick reference</see>. | ||
/// </summary> | ||
/// <param name="builder">The builder instance.</param> | ||
/// <param name="configureOptions">Configures options for the HTTP Streaming transport. This allows configuring per-session | ||
/// <see cref="McpServerOptions"/> and running logic before and after a session.</param> | ||
/// <returns>The builder provided in <paramref name="builder"/>.</returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception> | ||
public static IMcpServerBuilder WithHttpTransport(this IMcpServerBuilder builder, Action<HttpServerTransportOptions>? configureOptions = null) | ||
{ | ||
ArgumentNullException.ThrowIfNull(builder); | ||
builder.Services.TryAddSingleton<StreamableHttpHandler>(); | ||
|
||
if (configureOptions is not null) | ||
{ | ||
builder.Services.Configure(configureOptions); | ||
} | ||
|
||
return builder; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using ModelContextProtocol.Protocol.Transport; | ||
using System.Security.Claims; | ||
|
||
namespace ModelContextProtocol.AspNetCore; | ||
|
||
internal class HttpMcpSession | ||
{ | ||
public HttpMcpSession(SseResponseStreamTransport transport, ClaimsPrincipal user) | ||
{ | ||
Transport = transport; | ||
UserIdClaim = GetUserIdClaim(user); | ||
} | ||
|
||
public SseResponseStreamTransport Transport { get; } | ||
public (string Type, string Value, string Issuer)? UserIdClaim { get; } | ||
|
||
public bool HasSameUserId(ClaimsPrincipal user) | ||
=> UserIdClaim == GetUserIdClaim(user); | ||
|
||
// SignalR only checks for ClaimTypes.NameIdentifier in HttpConnectionDispatcher, but AspNetCore.Antiforgery checks that plus the sub and UPN claims. | ||
// However, we short-circuit unlike antiforgery since we expect to call this to verify MCP messages a lot more frequently than | ||
// verifying antiforgery tokens from <form> posts. | ||
private static (string Type, string Value, string Issuer)? GetUserIdClaim(ClaimsPrincipal user) | ||
{ | ||
if (user?.Identity?.IsAuthenticated != true) | ||
{ | ||
return null; | ||
} | ||
|
||
var claim = user.FindFirst(ClaimTypes.NameIdentifier) ?? user.FindFirst("sub") ?? user.FindFirst(ClaimTypes.Upn); | ||
|
||
if (claim is { } idClaim) | ||
{ | ||
return (idClaim.Type, idClaim.Value, idClaim.Issuer); | ||
} | ||
|
||
return null; | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/ModelContextProtocol.AspNetCore/HttpServerTransportOptions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using Microsoft.AspNetCore.Http; | ||
using ModelContextProtocol.Server; | ||
|
||
namespace ModelContextProtocol.AspNetCore; | ||
|
||
/// <summary> | ||
/// Configuration options for <see cref="M:McpEndpointRouteBuilderExtensions.MapMcp"/>. | ||
/// which implements the Streaming HTTP transport for the Model Context Protocol. | ||
/// See the protocol specification for details on the Streamable HTTP transport. <see href="https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http"/> | ||
/// </summary> | ||
public class HttpServerTransportOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets an optional asynchronous callback to configure per-session <see cref="McpServerOptions"/> | ||
/// with access to the <see cref="HttpContext"/> of the request that initiated the session. | ||
/// </summary> | ||
public Func<HttpContext, McpServerOptions, CancellationToken, Task>? ConfigureSessionOptions { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets an optional asynchronous callback for running new MCP sessions manually. | ||
/// This is useful for running logic before a sessions starts and after it completes. | ||
/// </summary> | ||
public Func<HttpContext, IMcpServer, CancellationToken, Task>? RunSessionHandler { get; set; } | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And we don't want it to automatically do WithHttpTransport? Could/should it do it but then if a user calls it themselves it overrides whatever default MapMcp would have used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's typical for things that need to configure routes like Mvc and SignalR to require that you call two methods: one to configure service, and then another to add the routes. For MVC, it's AddMvc and MapControllers/MapControllerRoutes. For SignalR, it's AddSignalR and MapHub. Even for minimal APIs, it's AddRouting, UseRouting, and MapGet/Post, but AddRouting and UseRouting get added explicitly for WebApplicationBuilder.
This is why I was so hesitant to add WithHttpTransport. The MapMcp call is the one thing we cannot easily get rid of. We could get rid of WithHttpTransport if we newed up the StreamableHttpHandler and any other services we need when they were missing, but it would be inconvenient for us as MapMcp implementers. And it is very conventional to need to call both an "Add" and a "Map" method, even if I'm personally not a fan of requiring the extra code.