Streamable HTTP is the current remote transport for the Model Context Protocol (MCP). The server exposes a single HTTP endpoint; the client sends each JSON-RPC message as an HTTP POST; the server responds with either a JSON body or an SSE stream on the same response when streaming is needed.
This guide builds a minimal ASP.NET Core Web API MCP server with Streamable HTTP in .NET and connects it to Claude Desktop. For protocol concepts, see What is the MCP Protocol?. For the older dual-endpoint design (/sse + /message), see Build MCP Server with SSE Transport.
Prerequisites
- .NET 8 SDK or later
- Basic familiarity with C# and HTTP APIs
- Optional: Claude Desktop for end-to-end testing
- Recommended: What is the MCP Protocol? — transports and client setup
- Optional: Server-Sent Events (SSE) and EventSource — when the server streams responses as
text/event-stream
How Streamable HTTP works
Streamable HTTP replaces the legacy split-channel model with one endpoint:
| Transport | Endpoints | Client → server | Server → client |
|---|---|---|---|
| Streamable HTTP | POST /mcp (or root) |
JSON-RPC in POST body | JSON or SSE on the same POST response |
| HTTP + SSE (legacy) | GET /sse + POST /message |
JSON-RPC to /message |
SSE frames on /sse |
Each client request is an HTTP POST. The server holds the response open and may write JSON-RPC results as standard SSE data: lines when streaming is required—natural HTTP backpressure until the handler completes.
Stateless vs stateful
The MCP C# SDK supports two HTTP session modes:
| Mode | Stateless |
Sessions | Best for |
|---|---|---|---|
| Stateless | true |
None — each POST is independent | Production, load balancers, horizontal scale |
| Stateful | false |
Mcp-Session-Id header tracked in memory |
Server-initiated messages, session resumption |
For most remote servers, set Stateless = true explicitly. Stateful mode requires session affinity when you scale out.
Complete example: Echo MCP server
Create the project
dotnet new web -n StreamableHttpMcpServer
cd StreamableHttpMcpServer
dotnet package add ModelContextProtocol.AspNetCore
Pin the port so client config stays predictable. In Properties/launchSettings.json:
"applicationUrl": "http://localhost:5131"
Define tools
Create Tools/EchoTools.cs:
using System.ComponentModel;
using ModelContextProtocol.Server;
[McpServerToolType]
public sealed class EchoTools
{
[McpServerTool, Description("Echoes the input back to the client.")]
public static string Echo(string message) => message;
}
Configure Program.cs
Streamable HTTP is the default when you use WithHttpTransport() without enabling legacy SSE. This minimal Web API host maps the MCP endpoint at /mcp:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = true;
})
.WithTools<EchoTools>();
var app = builder.Build();
app.MapMcp("/mcp");
app.Run();
WithHttpTransport()— registers Streamable HTTP (legacy/sseand/messagestay disabled).options.Stateless = true— recommended for production; no in-memory session map.MapMcp("/mcp")— clients connect tohttp://localhost:5131/mcp. UseMapMcp()without a path to serve at the application root instead.
Development host filtering
Limit accepted host names in development to reduce DNS rebinding risk. Add to appsettings.Development.json:
{
"AllowedHosts": "localhost;127.0.0.1;[::1]"
}
If you deploy behind a reverse proxy or tunnel, add the exact public host name—avoid "*".
Run and smoke test
Start the server:
dotnet run
The process listens on port 5131. MCP clients perform the full initialize handshake; a quick check that Kestrel is up:
curl -s -o /dev/null -w "%{http_code}" http://localhost:5131/mcp
A 405 (Method Not Allowed) on GET is expected—Streamable HTTP expects POST with JSON-RPC. When Claude or Cursor connects, you should see POST requests in the server terminal.
Test with Claude Desktop
Claude Desktop can connect directly to a Streamable HTTP URL when your build supports native HTTP MCP.
Prerequisites
- Claude Desktop installed
- The .NET MCP server running locally on port 5131
Configure Claude Desktop
Edit claude_desktop_config.json:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"streamable-echo": {
"url": "http://localhost:5131/mcp",
"type": "http"
}
}
}
Use the full path including /mcp when you called MapMcp("/mcp").
Step-by-step
- Start the server:
dotnet run(leave the terminal open) - Add the config above to
claude_desktop_config.json - Fully quit Claude Desktop and relaunch (config is read at startup)
- Open Claude and check the MCP/tools indicator (hammer icon)—
streamable-echoshould show as connected - Send a test prompt, for example: "Use the echo tool to repeat the phrase hello mcp"
- Confirm Claude calls
Echoand returns the echoed text
Verify and troubleshoot
- Claude logs: Help → View Logs (or Developer settings)—look for connection errors
- Server terminal: should log incoming HTTP POST requests when Claude connects
- Wrong URL: must match
MapMcppath (/mcpvs root) and port inlaunchSettings.json - Server not running: start the server before restarting Claude
- Stale config: quit Claude completely (not just close the window), then relaunch
- Firewall: ensure localhost traffic on port 5131 is allowed
For local single-user setups without HTTP, stdio is simpler—see Building MCP Server Using .NET.
Test with Cursor
Project-level .cursor/mcp.json or global ~/.cursor/mcp.json:
{
"mcpServers": {
"streamable-echo": {
"url": "http://localhost:5131/mcp"
}
}
}
Exact URL field names depend on your Cursor version; use the HTTP URL form your client documents for Streamable HTTP servers.
Streamable HTTP vs legacy SSE
| Aspect | Streamable HTTP | Legacy HTTP + SSE |
|---|---|---|
| Endpoints | One (POST /mcp) |
Two (GET /sse, POST /message) |
| SDK default | Yes (when legacy SSE disabled) | Opt-in via EnableLegacySse |
| Backpressure | POST held until handler completes | POST returns 202 immediately |
| New deployments | Recommended | Compatibility only |
Use Streamable HTTP for new remote services. Keep legacy SSE only when existing clients or mcp-remote bridges require /sse—see Build MCP Server with SSE Transport.
Production notes
- TLS — terminate HTTPS at ingress; do not expose plain HTTP in production.
- Authentication — protect the MCP endpoint with API keys, OAuth, or mutual TLS; validate
Originfor browser clients. - Stateless scaling — with
Stateless = true, any instance can handle any POST; no sticky sessions required. - Containers — expose the HTTP port, set
ASPNETCORE_URLS, and map/mcpthrough your ingress. See Run MCP Server in Docker for packaging patterns (adapt the Dockerfile for a web project instead of stdio).
Conclusion
Streamable HTTP is the straightforward path for remote MCP in .NET: a minimal Web API, WithHttpTransport(), and MapMcp("/mcp"). Clients POST JSON-RPC to one URL; the SDK handles JSON and SSE response modes. Prefer this transport for shared services; use stdio for local desktop integrations and legacy SSE only when you must.
Next steps
- Implement custom tools for your use case
- Add authentication and authorization
- Set up monitoring and logging
- Deploy behind HTTPS with Docker or your cloud platform
Related reading
- What is the MCP Protocol? — transports overview
- Building MCP Server Using .NET — stdio transport
- Build MCP Server with SSE Transport — legacy HTTP+SSE
- Server-Sent Events (SSE) and EventSource — SSE wire format
- Run MCP Server in Docker — container deployment
- .NET MCP transports — SDK reference