← All articles

Build MCP Server with Streamable HTTP in .NET

May 21, 2026 · 16 min read

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

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 /sse and /message stay disabled).
  • options.Stateless = true — recommended for production; no in-memory session map.
  • MapMcp("/mcp") — clients connect to http://localhost:5131/mcp. Use MapMcp() 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

  1. Start the server: dotnet run (leave the terminal open)
  2. Add the config above to claude_desktop_config.json
  3. Fully quit Claude Desktop and relaunch (config is read at startup)
  4. Open Claude and check the MCP/tools indicator (hammer icon)—streamable-echo should show as connected
  5. Send a test prompt, for example: "Use the echo tool to repeat the phrase hello mcp"
  6. Confirm Claude calls Echo and 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 MapMcp path (/mcp vs root) and port in launchSettings.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 Origin for 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 /mcp through 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

Related articles