In 2026, MCP has become the default protocol for Agents to connect to external systems. On the .NET side, there is a lack of a universal framework for “reference and openness”. McpFramework complements this layer: reference libraries, standard features, and one-line startup. Console, Web, WinForms, and WPF can all be controlled in real time by Agent through MCP.
This article first explains “what is MCP” clearly, and then gives the access method, the differences between the three types of transmission, the modification methods of the four types of projects, and a real SSE pitfall. All codes can be run, and the Agent side configuration is given at the end of the article.
1. Let’s make it clear first: what is MCP?
MCP (Model Context Protocol), launched by Claude’s parent company Anthropic, is an open protocol that connects large models with external tools, data sources, and application systems. It needs to solve a specific problem: large models could only rely on prompt words and static context before, and could not touch your files, databases, and APIs. MCP provides a standardized channel that allows the model to access these resources and perform operations on demand.
The most apt metaphor is USB-C. USB-C unifies the interface between devices and peripherals; MCP unifies the interface between models and external systems. With it, the same tool can be written once and reused by any host that supports MCP.

Figure: MCP is like a standard USB-C cable, one end is connected to the MCP client, and the other end is connected to the remote service and local data source.
Three parts: Host, Client, Server
MCP adopts a client-server architecture and is divided into three layers:
- MCP Host: The host application that initiates the connection, such as Claude Desktop, Cursor, and Cline.
- MCP Client: The communication layer in the host is responsible for establishing connections, discovering capabilities (Tools/Resources/Prompts), and converting model call requests into protocol messages.
- MCP Server: Capability provider encapsulates file systems, databases, and APIs into tools, and performs specific operations upon receiving requests.

Figure: A Host can host multiple Clients, each connected to a Server; the Server then connects to local files, databases or Internet APIs.
Three core components: Tools, Resources, Prompts
Server only exposes three types of things to the outside world:
- Tools: Executable capabilities that change the system state. For example, run commands, call APIs, and modify files. During model inference, it is decided which parameters to adjust and which parameters to pass.
- Resources: Read-only data that adds context to the model. Such as file content, database records, code warehouse. It does not perform operations, only provides information.
- Prompts: Standardized task template, stipulating “how to do” the model. With input and output schema, reusable.
One sentence to distinguish: Tools do the work, Resources provide materials, and Prompts set the rules.
Workflow
A complete call is divided into seven steps: establishing connection → negotiating rules → discovery capability → model thinking and selecting tools → client converts to standard format and sends → server calls external system for execution → result reflow and context update.
Figure: From left to right are users, MCP Client, MCP Server, and called external tools; the dotted lines represent the two-way flow of requests and results.
2. Three types of transmission: STDIO, SSE, Streamable HTTP
The protocol uses JSON-RPC, but there are three ways to transmit it at the bottom.

Figure: From top to bottom are LLM, MCP Client layer, transmission channel (STDIO / HTTP / WebSocket), MCP Server, and then to the external system.
| transmission | Applicable scenarios | endpoint | Features |
|---|---|---|---|
| STDIO | CLI, local process | process stdout | Direct connection between father and son processes, no network, low latency, strong isolation |
| SSE | Remote Services (old) | /sse + /message |
One-way flow, server push; additional POST channel required |
| Streamable HTTP | Remote Services (New) | /mcp |
POST request, can be returned once or streamed; easy to pass gateway and authentication |
STDIO is the default mode. As the parent process, the host launches the Server child process, sends requests through stdin, and receives responses through stdout. The whole process is network-free, just like USB direct plug. The price is that stdout must be pure JSON, and if the log is mixed in, the protocol frame will be destroyed.
SSE Based on HTTP long connection, the server pushes events in one direction. The implementation is simple and natural streaming, but the communication is one-way, and a POST interface is required to complete the request-response closed loop. It has been officially deprecated in the latest specification.
Streamable HTTP is an alternative and is now the recommended way. When the client makes a POST request, the server can return it all at once, or it can return intermediate results in a streaming manner on demand. It does not rely on a single long connection and is easier to connect to authentication, gateway, and load balancing.

Figure: The client POSTs a request, the server continues to push chunks after returning 200, and finally ends the stream with DONE.
3. The official SDK provides primitives but does not provide “one-click opening”
Let’s look at the hard data first. Microsoft ModelContextProtocol 2.3.0 and ModelContextProtocol.AspNetCore 2.3.0, corresponding to the 2026-07-28 protocol revision. In this version of the protocol, SEP-2567 cancels the Streamable HTTP session mechanism (Mcp-Session-Id removed).
These two packages only contain server-side types. Our self-test client wants to call McpClientFactory,exist ModelContextProtocol 2.3.0 I searched all over and couldn’t find it, so in the end I had to use bare JSON-RPC. This is not the SDK’s fault, it is only responsible for protocol primitives.
The current situation of the community is not blank either. MCPSharp uses attribute-based .NET services; Runiq.AI.Mcp binds the ASP.NET Core pipeline; Python has FastMCP. However, .NET lacks a universal lib that is “embedded and open” – if you reference it, you can turn the methods in the application into MCP tools without reading the protocol or writing the transmission.
McpFramework’s positioning is very narrow: thinly encapsulating the official SDK and reducing “open capabilities” from reading protocols to standard features. It does not create agreements, only experiences.
4. Three steps of installation and access
Preparation: Install McpFramework
The package has been published to NuGet, package name McpFramework. One command to install:
dotnet add package McpFramework
or handwritten PackageReference:
The package is declared in nuspec Microsoft.AspNetCore.App The frame reference will be automatically brought in by the consumer, so there is no need to add it manually. it relies on official ModelContextProtocol 2.3.0 and ModelContextProtocol.AspNetCore 2.3.0,dotnet restore Pull them all together.
If it has not been uploaded to nuget.org, you can install it from local sources first: first
McpFramework.1.0.0.nupkgPut it into a directory and execute itdotnet add package McpFramework --source <该目录>。
Step 1: Mark the method as a tool
using System.ComponentModel;
using ModelContextProtocol.Server;
[McpServerToolType]
public static class MyTools
{
[McpServerTool, Description("设置设备状态文本")]
public static string SetStatus([Description("新文本")] string text)
{
// 这里改你自己的运行时状态
return "ok";
}
[McpServerTool, Description("读取当前状态")]
public static string GetStatus() => "running";
}
[McpServerToolType] marked on the class,[McpServerTool] marked on the method,[Description] It is an instruction manual written for the Agent. How many parameters does the method have and what type of parameters are they? The SDK will automatically generate a JSON Schema, and the Agent will pass the parameters according to the schema.
Step 2: Start with one line
Stdio is suitable for CLI and is started by Agent as a child process:
using McpFramework;
await McpServer.StartAsync(new McpFrameworkOptions
{
Transport = McpTransport.Stdio,
ServerName = "ConsoleApp",
Version = "1.0.0"
});
Http is suitable for resident programs, and the MCP endpoint runs with the application:
await McpServer.StartAsync(new McpFrameworkOptions
{
Transport = McpTransport.Http,
HttpPort = 5200,
ServerName = "WinFormsApp",
Version = "1.0.0",
EnableLegacySse = true // 同时暴露 SSE 端点,见第七节
});
HttpHost default http://localhostchange the port in one line.Version will appear in initialize of serverInfo Here, it is convenient for the Agent to distinguish who it is.
Step 3: Let the Agent connect
exist mcp.json Just write two lines, one each for Stdio and Http:
{
"mcpServers": {
"webapp": { "url": " },
"winforms": { "url": " },
"wpf": { "url": " },
"console": {
"command": "dotnet",
"args": ["src/ConsoleApp/bin/Debug/net8.0/ConsoleApp.dll"]
}
}
}
Key points: HTTP class walk urlthe Stdio class goes command. If the client only supports SSE (Section 7 is dedicated to this topic), put url Replace with 。
5. How to modify the four types of projects (with complete code)
Web(Razor Pages)
using McpFramework;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSingleton();
builder.Services.AddMcpFramework(new McpFrameworkOptions
{
Transport = McpTransport.Http,
ServerName = "WebApp",
Version = "1.0.0",
EnableLegacySse = true
});
var app = builder.Build();
app.MapRazorPages();
app.MapMcpFramework(new McpFrameworkOptions { Transport = McpTransport.Http });
app.Run("
AddMcpFramework Responsible for registration,MapMcpFramework Responsible for hanging endpoints, both share a request pipeline with Razor Pages. The tool class supports constructor dependency injection and gets the status directly from the container:
[McpServerToolType]
public sealed class WebTools
{
private readonly WebAppState _state;
public WebTools(WebAppState state) => _state = state;
[McpServerTool, Description("设置站点标题,并返回确认。")]
public string SetTitle([Description("新标题")] string title)
{
_state.Title = title;
return $"title set: {title}";
}
[McpServerTool, Description("读取当前站点标题。")]
public string GetTitle() => _state.Title;
[McpServerTool, Description("返回已注册的页面列表。")]
public string[] GetPageList() => new[] { "/Index", "/Privacy", "/Error" };
}
In this way, if the Agent changes the title, the new value can be read when the Razor page is rendered – the state is the same singleton.
WinForms: Control.Invoke marshalling
The MCP tool runs in the background thread of Kestrel, and directly changing the control will throw a cross-thread exception. Must switch back to the UI thread:
[McpServerToolType]
public static class WinFormsTools
{
[McpServerTool, Description("设置主窗体标签文本。")]
public static string SetLabel([Description("新标签文本")] string text)
{
var form = AppContextHolder.Form;
if (form is null) return "error: form not ready";
form.Invoke((Action)(() => form.SetLabel(text)));
return "label updated";
}
[McpServerTool, Description("读取当前标签与可见状态。")]
public static object GetFormState()
{
var form = AppContextHolder.Form;
if (form is null) return new { status = "not ready" };
return form.Invoke((Func
Let Kestrel run in parallel with the UI thread when starting, close the form and then cancel the token:
var cts = new CancellationTokenSource();
var serverTask = McpServer.StartAsync(new McpFrameworkOptions
{
Transport = McpTransport.Http,
HttpPort = 5200,
ServerName = "WinFormsApp",
EnableLegacySse = true
}, cts.Token);
ApplicationConfiguration.Initialize();
var form = new MainForm();
AppContextHolder.Form = form;
form.FormClosed += (_, _) => cts.Cancel();
Application.Run(form);
await serverTask;
WPF: Dispatcher.Invoke marshalling
WPF is the same, just replace it with Dispatcher:
[McpServerToolType]
public static class WpfTools
{
[McpServerTool, Description("设置主窗口状态文本。")]
public static string SetText([Description("新文本")] string text)
{
var w = WpfContext.Window;
if (w is null) return "error: window not ready";
w.Dispatcher.Invoke(() => w.SetStatus(text));
return "text updated";
}
[McpServerTool, Description("读取主窗口当前状态文本。")]
public static object GetViewModel()
{
var w = WpfContext.Window;
if (w is null) return new { status = "not ready" };
return w.Dispatcher.Invoke(() => (object)new { text = w.Status })!;
}
}
The startup of WPF is placed in App.OnStartup inside,OnExit When canceling the token:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow();
WpfContext.Window = window;
_cts = new CancellationTokenSource();
_ = McpServer.StartAsync(new McpFrameworkOptions
{
Transport = McpTransport.Http,
HttpPort = 5300,
ServerName = "WpfApp",
EnableLegacySse = true
}, _cts.Token);
MainWindow = window;
window.Show();
}
Console: Stdio default, –http switches to SSE
Two ways to run one program. Stdio pulls child processes to the Agent.--http Directly connect to the browser or SSE client for debugging:
using McpFramework;
var useHttp = args.Contains("--http", StringComparer.OrdinalIgnoreCase);
if (useHttp)
{
Console.Error.WriteLine("ConsoleApp (HTTP+SSE): | /mcp/sse");
await McpServer.StartAsync(new McpFrameworkOptions
{
Transport = McpTransport.Http,
HttpPort = 5400,
ServerName = "ConsoleApp",
EnableLegacySse = true
});
}
else
{
Console.Error.WriteLine("ConsoleApp (Stdio) 启动,等待 Agent 接入。");
await McpServer.StartAsync(new McpFrameworkOptions
{
Transport = McpTransport.Stdio,
ServerName = "ConsoleApp"
});
}
In Stdio mode, the frame will automatically Logging.ClearProviders()——Because stdout is a JSON-RPC channel, any log written into it will destroy the protocol frame. This is a pitfall that the framework blocks for developers.
6. End-to-end actual measurement
All four projects are connected and verified:
| project | transmission | endpoint | exposed tools |
|---|---|---|---|
| ConsoleApp | Stdio / HTTP+SSE | child process / :5400/mcp |
echo / add / get_time / set_message / get_message |
| WebApp | HTTP | :5000/mcp + /mcp/sse |
set_title / get_title / get_page_list / get_visitors |
| WinFormsApp | HTTP | :5200/mcp + /mcp/sse |
show_message / set_label / get_form_state |
| WpfApp | HTTP | :5300/mcp + /mcp/sse |
show_message / set_text / get_view_model |
Self-test client McpSmokeTest(Stdio + HTTP mixed) output ALL TESTS PASSED;dotnet build TestMCP.sln 0 warnings 0 errors. Three HTTP programs /mcp/sse all return 200 + Content-Type: text/event-stream。
7. SSE handshake record
SSE transmission is divided into two channels: GET to establish a stream, and POST to send messages. Use curl to go through it completely:
# 1) 建立 SSE 流。连上后服务端先推一个 endpoint 事件,告知 POST 地址
curl -N -H "Accept: text/event-stream"
# 服务端立即回:
# HTTP/1.1 200 OK
# Content-Type: text/event-stream
#
# event: endpoint
# data: /mcp/message?sessionId=_rqTQHN_kGpN58zfkhIZuA
# 2) 把 JSON-RPC 消息 POST 到那个地址。返回 202 Accepted,响应走 SSE 流回传
curl -X POST " \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"set_label","arguments":{"text":"SSE 控制 WinForms"}}}'
# 3) SSE 流里收到结果
# event: message
# data: {"result":{"content":[{"type":"text","text":"label updated"}]},"id":2,"jsonrpc":"2.0"}
POST again get_form_statestream back {"label":"SSE 控制 WinForms","visible":true}. This shows that SSE transmission and UI thread grouping are established at the same time – the label is really changed and the status is really read.
8. A real pitfall: SSE and Streamable do not match
When connecting WebApp to WorkBuddy, an error occurs SSE error: Invalid content type, expected "text/event-stream"。
The reason is not complicated: the client uses legacy SSE and sends GET and expect text/event-stream;The server only enables Streamable HTTP, POST-only, and GET is disabled in Stateless mode. Actual measurement GET /mcp return 405 Method Not Allowedin response to Lilian Content-Type None. The client gets a non-SSE response, and the parser reports an error directly.
Repair only does two things: open EnableLegacySseand then change the client URL from /mcp Replace with /mcp/sse. Comparison after modification:
| ask | Before the change | After modification |
|---|---|---|
GET /mcp |
405 Method Not Allowed |
— |
GET /mcp/sse |
— | 200,Content-Type: text/event-streamreceived first event: endpoint |
After reconnection initialize and tools/list Complete access, all four tools available. This pitfall is worth writing about because “SSE or Streamable” is the most frequent mismatch for MCP access in 2026.
9. Agent side configuration
Four kinds of servers are in mcp.json In the way of writing, please note that the SSE client must point to /mcp/sse:
{
"mcpServers": {
"webapp": { "url": " },
"webapp-sse": { "url": " },
"winforms": { "url": " },
"wpf-sse": { "url": " },
"console-sse": { "url": "/sse" },
"console": {
"command": "dotnet",
"args": ["src/ConsoleApp/bin/Debug/net8.0/ConsoleApp.dll"]
}
}
}
The same program can be connected to two programs at the same time: go to the Streamable client. /mcponly recognize SSE clients. /mcp/ssedo not interfere with each other.
in conclusion
McpFramework reduces “opening capabilities to Agent” from reading protocols to standard features. Four types of .NET programs, three types of transmissions, and two target frameworks have been tested and feasible: net8.0 can get HTTP and SSE, netstandard2.0 can get Stdio, and old projects of .NET Framework 4.8 can also be accepted. It does not solve all problems, but it solves the most common step: turning an ordinary .NET program into an Agent-controllable endpoint in a few lines of code.
The distribution step has been completed. The package is above, the package ID McpFramework, the latest version is 1.1.0, one line dotnet add package McpFramework can be used. The next two things: Supplement the official client package so that self-testing and secondary development do not have to deal with JSON-RPC by hand; group and extract the UI threads of WinForms/WPF into base classes, eliminating the need to repeatedly write Invoke templates for each project.
Package address: . After installing it, it still doesn’t work. If I post the return code and Content-Type of GET /mcp, I can basically locate whether it is the transmission mismatch in Section 8.
Picture source: Tencent Cloud Developer Community “What exactly is MCP?” This article will help you understand it thoroughly” (author Al1ex), just for technical instructions.
Focus on Internet product development and share tool experience. Do everything well, big or small.