feat: expose StreamableHttpHandler for MVC controller support#1311
feat: expose StreamableHttpHandler for MVC controller support#1311jtarquino wants to merge 1 commit intomodelcontextprotocol:mainfrom
Conversation
Make StreamableHttpHandler public so it can be injected into traditional ASP.NET Core MVC controllers, enabling MCP server scenarios without minimal APIs. Changes: - StreamableHttpHandler: internal -> public, constructor takes IServiceProvider - Updated WithHttpTransport() XML docs to mention controller injection - Added 'Using with MVC Controllers' section to AspNetCore README - New sample: AspNetCoreMcpControllerServer - New integration tests: McpControllerTests (connect, call tool, list tools) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
@mikekistler @stephentoub Do you have any opinions on introducing a public API like the following for use in custom endpoints like controller actions?
namespace ModelContextProtocol.AspNetCore;
public interface IStreamableHttpHandler
{
Task HandlePostRequestAsync(HttpContext context);
Task HandleGetRequestAsync(HttpContext context);
Task HandleDeletetRequestAsync(HttpContext context);
}
// Or just
public interface IStreamableHttpHandler
{
Task HandleRequest(HttpContext);
}
// Or maybe instead of registering it as a singleton, we could have a factory
public static class McpRequestDelegateFactory
{
public RequestDelegate Create(McpServerOptions? serverOptions = null,
HttpServerTransportOptions? transportOptions = null,
IServiceProvider? applicationServices = null);
}I was hesitant to do this right after MCP's transition from "HTTP+SSE" to "Streamable HTTP" as the web-based transport, because I wasn't sure if a new kind of transport would cause us to want to create a completely different kind of interface.
I'm leaning towards my last proposal with the factory. I think having a way to create a RequestDelegate from McpServerOptions makes a lot of sense though. It's very general, so long as you can handle the routing without any input from the SDK. It reminds me a bit of RequestDelegateFactory.Create
| "2025-11-25", | ||
| ]; | ||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public StreamableHttpHandler(IServiceProvider serviceProvider) |
There was a problem hiding this comment.
I understand not wanting to add a public constructor with a bunch of parameters, but this forces StremableHttpHandler to use the service locator pattern which is generally considered bad practice. Not that constructing a singleton is a perf hotspot, but it's generally better to give the DI/IoC container more detailed knowledge of the service dependencies.
If we decide to make something like this public, I would sooner introduce an IStreamableHttpHandler interface and leave the implementation internal.
Motivation and Context
The
ModelContextProtocol.AspNetCorepackage currently only exposes MCP HTTP endpoints viaMapMcp()— a minimal API extension method. Users whose applications rely exclusively on traditional MVC controllers (AddControllers()+MapControllers()) cannot integrate MCP into their ASP.NET Core apps without also adopting minimal APIs. This change makesStreamableHttpHandlerpublic so users can inject it into their own controllers and delegate the POST/GET/DELETE requests directly.How Has This Been Tested?
McpControllerTests) covering connect, tool call, and tool listing via an MVC controller — all passing on net8.0, net9.0, and net10.0.initialize,notifications/initialized,tools/list, andtools/call(echo) via HTTP POST against the MVC controller endpoint.Breaking Changes
None. This is an additive change — an
internalclass was madepublic. No existing public APIs were modified.Types of changes
Checklist
Additional context
StreamableHttpHandlerwas converted from a primary constructor to a regular constructor takingIServiceProviderto avoid exposing internal types (StatefulSessionManager,StreamableHttpSession) in the public API surface.[EditorBrowsable(EditorBrowsableState.Never)]since it's intended for DI resolution only.samples/AspNetCoreMcpControllerServer/) demonstrates the pattern.