Skip to content

Comments

feat: MVC controller support#1311

Open
jtarquino wants to merge 1 commit intomodelcontextprotocol:mainfrom
jtarquino:feature/mvc-controller-support
Open

feat: MVC controller support#1311
jtarquino wants to merge 1 commit intomodelcontextprotocol:mainfrom
jtarquino:feature/mvc-controller-support

Conversation

@jtarquino
Copy link

@jtarquino jtarquino commented Feb 19, 2026

Adds McpRequestDelegateFactory — a static factory that creates a RequestDelegate for handling MCP Streamable HTTP transport requests. This enables MCP integration with traditional MVC controllers and other request-handling patterns without requiring minimal APIs (MapMcp()).

Motivation and Context

The ModelContextProtocol.AspNetCore package currently only exposes MCP HTTP endpoints via MapMcp() — a minimal API extension method. Users whose applications rely on traditional MVC controllers (AddControllers() + MapControllers()) cannot integrate MCP without also adopting minimal APIs. This change adds a McpRequestDelegateFactory.Create() method that returns a standard RequestDelegate, following the same pattern as ASP.NET Core's own RequestDelegateFactory.

Usage

[ApiController]
[Route("mcp")]
public class McpController : ControllerBase
{
    private static readonly RequestDelegate _mcpHandler = McpRequestDelegateFactory.Create();

    [HttpPost]
    [HttpGet]
    [HttpDelete]
    public Task Handle() => _mcpHandler(HttpContext);
}

How Has This Been Tested?

  • 3 new integration tests (McpControllerTests) covering connect, tool call, and tool listing via an MVC controller — all passing on net9.0.
  • Full existing test suite passes (1,825 core + 292 AspNetCore + 57 Analyzers tests, 0 failures).
  • Manual end-to-end test: launched the sample server and successfully executed initialize, notifications/initialized, tools/list, and tools/call (echo) via HTTP POST against the MVC controller endpoint.

Breaking Changes

None. This is a purely additive change — a new public static class McpRequestDelegateFactory is added. No existing APIs were modified.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

  • Based on review feedback from @halter73, this uses the factory pattern (McpRequestDelegateFactory) instead of exposing the handler directly or via an interface. This approach is future-proof — if the MCP transport changes, the RequestDelegate abstraction remains stable.
  • StreamableHttpHandler remains internal with its original primary constructor — no internal types are exposed.
  • A new sample project (samples/AspNetCoreMcpControllerServer/) demonstrates the pattern.
  • The README includes a "Using with MVC Controllers" section.

Copy link
Contributor

@halter73 halter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

@jtarquino jtarquino force-pushed the feature/mvc-controller-support branch 2 times, most recently from 6bedd92 to 8b5f0a7 Compare February 23, 2026 21:15
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>
@jtarquino jtarquino force-pushed the feature/mvc-controller-support branch from 8b5f0a7 to d25782f Compare February 23, 2026 21:23
@jtarquino jtarquino changed the title feat: expose StreamableHttpHandler for MVC controller support feat: MVC controller support Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants