C# 파일 하나로 끝내는 MCP 서버 만들기

지속적으로 ModelContextProtocol SDK의 버전이 업데이트되면서, 이제는 Native AOT 시나리오로도 사용할 수 있게 개선되었습니다. 그래서 아래와 같이 매우 간결한 .NET 10의 File-based app 스타일의 MCP 서버 코드를 빌드 옵션 변경 없이 작성할 수 있었고 매우 잘 작동합니다.

다만 Virtual Project로 빌드가 이루어지다보니, 코드를 다시 빌드할 일이 발생할 경우 여러 MCP 서버를 동시에 사용하려 할 때 stdout에 msbuild의 출력이 나타나다보니 첫 로딩 이후 후속으로 로딩하려는 MCP 클라이언트에서는 오류가 발생할 수 있다는 known-issue를 고려해야 하는 점이 있었습니다.

#!/usr/bin/env dotnet
#:sdk Microsoft.NET.Sdk.Web
#:package ModelContextProtocol@0.3.0-preview.4

// 노트: dotnet run .cs 명령으로는 이 MCP 서버를 동시에 여러 클라이언트에서 사용하지 못할 수 있습니다.

using System.ComponentModel;
using ModelContextProtocol.Server;

var builder = Host.CreateEmptyApplicationBuilder(default);
builder.Configuration.AddCommandLine(args);
builder.Configuration.AddEnvironmentVariables();
builder.Services.AddHttpClient();
builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools([
        McpServerTool.Create(IpAddressTool),
    ]);

var app = builder.Build();
app.Run();

[Description("Get the public IP address of this machine.")]
async Task<string> IpAddressTool(
    IServiceProvider services,
    [Description("Get IPv6 address instead of IPv4 address")] bool ipv6)
{
    try
    {
        var client = services.GetRequiredService<HttpClient>();
        return await client.GetStringAsync(
            ipv6 ? "https://api6.ipify.org" : "https://api.ipify.org"
            ).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        return $"Error occurred: {ex.Message}";
    }
}

그리고 다음과 같이 MCP 서버 설정 JSON을 추가하여 연동할 수 있습니다.

...
	"servers": {
		"my-mcp-server-a983fe19": {
			"type": "stdio",
			"command": "dotnet",
			"args": [
				"run",
				"--arch",
				"x64",
				"C:\\Users\\rkttu\\Desktop\\simpleweb\\McpTest.cs"
			]
		}
	},
...

좀 더 안정적인 환경을 구현하려면, dotnet publish 명령으로 EXE 파일을 만들어서 MCP 서버를 사용하는 것이 유용한 것 같습니다.

11개의 좋아요

정현님은 file-based app이 정말 마음에 드시나 보군요. 이렇게 많은 것들을 file-based app으로 작성하시는 것을 보면…

4개의 좋아요

그렇습니다. 닷넷에 대해서 그동안 답답했던 부분들을 속시원하게 뚫어주는 부분이라고 생각하여 여기에 요즈음 진심인 상태입니다. ㅎㅎ

3개의 좋아요

저는 C# script(csx)의 #load같은 것만 있다면 더 완벽할 것 같다고 생각합니다.

3개의 좋아요

그러게요! 다만 프로젝트 기반 시스템이라는 점을 고려했을 때, Directory.build.props 파일이나 다른 파일을 해당 디렉터리에 같이 넣어두면 인식해서 자동으로 반영하는 것은 나름 대안이라고 할 수 있을 것 같습니다.

관련 내용: Behind the scenes of dotnet run app.cs

3개의 좋아요

그리고 재미있는 사실을 하나 더 알았는데, 출력 타입이 콘솔이 아니어도 MCP 서버를 구동할 수 있습니다. StdioTransport의 경우 WinMain 메서드로 시작을 하더라도 stdout을 읽도록 프로세스를 띄운 상태라면 MCP를 위한 커뮤니케이션은 화면에 보여지는 내용없이 백그라운드에서 정상적으로 주고 받을 수 있습니다.

즉, (일단 Windows에서는) GUI 애플리케이션을 MCP 서버로 만드는 것이 가능합니다. StdioTransport로도 가능하고, HttpTransport로도 가능합니다.

MCP 서버라는 것 자체는 그래서 예전의 Out-of-Proc COM 서버가 AI 시대에 다시 태어난 것에 가까운 것이라 생각하게 되네요. (엄밀히 말하면 디스커버리나 액티베이션은 빠져있는 것이지만 동작 방식은 그런 것을 떠올리게 합니다 ㅎㅎ) :thinking:

3개의 좋아요

WinMain의 MCP 서버라고 하니
약간 옛날 방식으로 주로 쓰이던
Windows forms 로 띄워 놓은 TCP 서버 (겉보기엔 관리 콘솔 GUI가 포함된…ㅋㅋ)가 생각나네요 ㅎㅎ

2개의 좋아요

file-based로 할수있는거중에 혹시 CGI까지 다뤄주실수 있을까요

3개의 좋아요