일타 저스틴의 MCP 강의 04-001. Model Context Protocol (MCP) C# SDK
오늘 강의에서는 “MCP 공식 C# SDK의 NuGet 패키지 ModelContextProtocol
에 대해 설명해 드리겠습니다.
1. MCP와 SDK 개요
먼저 MCP가 무엇인지부터 짚고 넘어가야겠죠? MCP는 애플리케이션이 LLM에 컨텍스트를 표준화된 방식으로 제공하도록 설계된 오픈 프로토콜입니다. 즉, LLM이 더 똑똑해지도록 도와주는 ‘정보 교환의 규칙’인 셈이죠.
MCP를 .NET에서 쉽게 쓰도록 만든 C# SDK는 크게 세 가지 패키지로 구성되어 있어요.
- ModelContextProtocol: 기본 패키지로, 호스팅과 의존성 주입 확장 기능 제공. HTTP 서버 기능이 필요 없는 대부분 프로젝트에 적합합니다.
- ModelContextProtocol.AspNetCore: HTTP 기반 MCP 서버를 위한 라이브러리.
- ModelContextProtocol.Core: 클라이언트나 저수준 서버 API만 필요할 때 사용하며, 의존성 최소화.
2. 클라이언트 시작하기
SDK에서 클라이언트를 만드는 방법도 매우 직관적입니다. McpClient.CreateAsync
메서드로 클라이언트를 생성하고 서버에 연결합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var clientTransport = new StdioClientTransport(new StdioClientTransportOptions { Name = "Everything", Command = "npx", Arguments = ["-y", "@modelcontextprotocol/server-everything"], }); var client = await McpClient.CreateAsync(clientTransport); // 서버에서 제공하는 도구 목록 출력 foreach (var tool in await client.ListToolsAsync()) { Console.WriteLine($"{tool.Name} ({tool.Description})"); } // "echo" 도구 실행 예 var result = await client.CallToolAsync( "echo", new Dictionary<string, object?>() { ["message"] = "Hello MCP!" }, cancellationToken: CancellationToken.None); Console.WriteLine(result.Content.First(c => c.Type == "text").Text); |
여기서 핵심은 MCP 클라이언트가 다양한 도구(툴)를 나열하고 호출할 수 있다는 점입니다. LLM이 도구를 불러 쓰듯 클라이언트도 서버 도구를 호출하는 구조죠.
3. 서버 시작하기
이 글의 저자는 MCP 서버를 만드는 법도 상세히 보여줍니다. Microsoft.Extensions.Hosting
패키지를 쓰고, AddMcpServer()
와 같은 편리한 확장 메서드를 통해 서버를 빠르게 구성합니다.
서버 예제 중 매우 중요한 부분은 아래의 EchoTool
입니다.
1 2 3 4 5 6 7 |
[McpServerToolType] public static class EchoTool { [McpServerTool, Description("Echoes the message back to the client.")] public static string Echo(string message) => $"hello {message}"; } |
MCP 서버 도구는 이렇게 어트리뷰트를 붙여서 쉽게 정의하고, 자동으로 등록할 수 있습니다. 서버와 클라이언트가 주고받는 메시지의 인터페이스를 명확히 하는 좋은 방법입니다.
4. 고급 서버 기능
또한, 서버 도구 메서드에 McpServer
인스턴스나 HttpClient
같은 의존성을 주입할 수 있어요. 예를 들어 URL에서 콘텐츠를 다운로드해 요약하는 도구는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[McpServerTool(Name = "SummarizeContentFromUrl"), Description("Summarizes content downloaded from a specific URI")] public static async Task<string> SummarizeDownloadedContent( McpServer thisServer, HttpClient httpClient, string url, CancellationToken cancellationToken) { string content = await httpClient.GetStringAsync(url); ChatMessage[] messages = [ new(ChatRole.User, "Briefly summarize the following downloaded content:"), new(ChatRole.User, content), ]; ChatOptions options = new() { MaxOutputTokens = 256, Temperature = 0.3f, }; return $"Summary: {await thisServer.AsSamplingChatClient().GetResponseAsync(messages, options, cancellationToken)}"; } |
이처럼 서버 도구는 LLM과의 상호작용을 포함해 복잡한 처리를 유연하게 할 수 있습니다.
5. 프롬프트 정의
프롬프트도 어트리뷰트로 정의할 수 있습니다.
1 2 3 4 5 6 7 8 |
[McpServerPromptType] public static class MyPrompts { [McpServerPrompt, Description("Creates a prompt to summarize the provided message.")] public static ChatMessage Summarize(string content) => new(ChatRole.User, $"Please summarize this content into a single sentence: {content}"); } |
클라이언트와 서버는 아래와 같은 표준 메시지들을 주고받으며 상호작용합니다.
메시지 | 설명 |
---|---|
InitializeRequest | 클라이언트가 서버에 처음 연결할 때 서버에게 초기화를 시작하도록 요청합니다. |
ListToolsRequest | 클라이언트가 서버가 가진 도구 목록을 요청합니다. |
CallToolRequest | 클라이언트가 서버가 제공하는 도구를 호출하는 데 사용됩니다. |
ListResourcesRequest | 클라이언트가 사용 가능한 서버 자원 목록을 요청합니다. |
ReadResourceRequest | 클라이언트가 특정 자원 URI를 읽기 위해 서버에 전송합니다. |
ListPromptsRequest | 클라이언트가 서버로부터 사용 가능한 프롬프트 및 프롬프트 템플릿 목록을 요청합니다. |
GetPromptRequest | 클라이언트가 서버가 제공하는 프롬프트를 가져오는 데 사용됩니다. |
PingRequest | 서버 또는 클라이언트가 상대방이 여전히 활성 상태인지 확인하기 위해 보냅니다. |
CreateMessageRequest | 서버가 클라이언트를 통해 LLM을 샘플링하기 위한 요청입니다. 클라이언트는 어떤 모델을 선택할지 전적인 재량권을 가집니다. 또한, 클라이언트는 샘플링을 시작하기 전에 사용자에게 요청을 검토하고 승인 여부를 결정할 수 있도록(human in the loop) 알려야 합니다. |
SetLevelRequest | 클라이언트가 서버에 로깅을 활성화하거나 조정하도록 요청합니다. |