Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

MCP(Model Context Protocol)는 AI 모델과 외부 데이터 소스, 도구를 연결하는 표준 프로토콜로 AI가 사용자의 작업 문맥을 이해하고 다양한 정보를 전달할 수 있도록 해주며

닷넷에서 로컬에 작동하는 MCP EchoServer를 먼저 만들어본후 MCP에 액터모델을 연결해 Client-Server Model로 확장하는 변종실험으로  간단한 노트작성및 검색기능을 탑재해보았습니다.


콘솔로 새프로젝트

Image Modified

  • 타켓 프레임워크 : 9.0 으로하기

      ...

        • 사용된 ModelContextProtocol-Preview 버전이 타깃이 프레임워크 9.0부터 지원을 해서~ 9.0으로 출발


      필요  Nuget 패키지

      • ModelContextProtocol - MCP작성을 위한 패키지
      • ModelContextProtocol.NET.Server - 작성된 MCP를 표준 MCP 서버로 구동시키기위한 패키지

      Image Modified

      Image Modified

      Image Removed

      Image Removed

      Image Removed

      Image Removed



      MCP Server APP 작동코드

      Code Block
      themeEmacs
      {
          "mcp": {
              "servers": using Microsoft.Extensions.DependencyInjection;
      using Microsoft.Extensions.Hosting;
      using Microsoft.Extensions.Logging;
      
      var builder = Host.CreateApplicationBuilder(args);
      builder.Logging.AddConsole(consoleLogOptions =>
      {
          // Configure all logs to go   "McpServer": {to stderr
          consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
      });
      
      builder.Services
          .AddMcpServer()
          "type": "stdio",.WithStdioServerTransport()
                    "command": "dotnet",
                    "args": [
                      "run",
        .WithToolsFromAssembly();
      
      await builder.Build().RunAsync();
      • StdIO모드로 Local LLM Agent툴과 상호작용하는듯
      • 특정 Port가 Listen되어 Remote로 서비스를 제공하는 일반적인 Server 개념과는 다릅니다.
        • 처음 MCP Server 구동할때 서버는  Port는 도대체 뭐지? - 삽질주의 PartA
      • LLM Agent가 필요하면 필요한 타이밍 툴을 실행하고 종료하는 짧은 사이클을 가졌습니다.
        • 아주 짧은 사이클을 가졌으며~ 롱텀이라 생각했던 싱글톤사이클의 DI를 주입하더라도~ 객체가 금방사라져 삽질을 하게됩니다.  - 삽질주의 PartB
      • 계속 지속 작동하는 Server로 인식하기보다 LLM Agent가 필요하면 잠깐 당겨써는~ Execute Tool이다라고 생각해야 여러모로 헛갈리지 않을듯

      MCP 코드

      Code Block
      themeEmacs
      using System.ComponentModel;
      using ModelContextProtocol.Server;
      
      namespace McpServer.Tools;
      
      [McpServerToolType]
      public static class EchoTool
      {
          [McpServerTool, Description("Echoes the message back to the client.")]
          public static string Echo(string message) => $"Hello from C#: {message}";
      
          [McpServerTool, Description("Echoes in reverse the message sent by the client.")]
          public static string ReverseEcho(string message) => new string(message.Reverse().ToArray());
      }
      • McpServerToolType을 인식해 MCP코드를 작동시켜주며~ Tools디렉토리하위에 Tool단위로 구성파일을 추가



      Description은 이것을 이용하는 LLM에게 툴의 설명을 주며 , LLM과 상호작용해 해당기능을 수행합니다.

      MCP TestTool로는 VisualStudio Code + Copilot이 이용되었습니다.

      VisualStudio Code

      Image Added

      • Agent모드로 전환해줍니다.



      Image Added

      • Add MCP Server를 해줍니다.


      Visual Studio에 MCP 작동코드를 추가합니다.

      Image Added

      Code Block
      themeEmacs
      {
          "mcp": {
              "servers": {
                  "McpServer": {
                    "type": "stdio",
                    "command": "dotnet",
                    "args": [
                      "run",
                      "--project",
                      "D:\\Code\\Webnori\\NetCoreLabs\\McpServer\\McpServer.csproj",
      				"--no-build",               
      				]
                  }
                }
          }
      }
      • 경로는 윈도우 기준 제 폴더이니~ dotnet run 프로젝트 경로를 맞추어줍니다.
        • 닷넷작업 본 IDE인  VisualStudio또는 Jetbrain Rider에서만 변경빌드를 해주려고, 빌드없이 실행인 no-build옵션을 선택했습니다.
      • 다 작성되면 Running 로 MCP를 Local 실행할수 있습니다. 
      • py uv 패키지를 이용해 publish하면 더 쉽게 보급하는듯 -여기서는 로컬닷넷 개발환경이 이용되었습니다.

      MCP Tool 선택

      Image Added

      • MCP Server가 잘등록되고 나면~ 코파일럿 Agent모드일때 우리가 작성한 Tool을 선택할수 있습니다.

      TEST

      Image Added

      • 우리가 작성한 MCP 기능을 인식해 LLM을 통해 자연어 수행을 할수 있게됩니다.

      여기까지가 닷넷을 통해 MCP-Server 를 만들고 테스트해보는 가장 심플한 방법이자 가장 많이 알려진 방법입니다.


      MCP에 노트작성기능과 검색기능 그리고 사용히스토리기능을 하는 액터베이스의 서버기능을 만들어 MCP-Server를 업그레이드 시도해보겠습니다.


      액터모델과 함께 시도된 변종 MCP 구성도

      draw.io Board Diagram
      bordertrue
      diagramNamemcp with actor
      simpleViewerfalse
      width
      linksauto
      tbstyletop
      lboxtrue
      diagramWidth1446
      revision4

      • MCP Client : MCP Tool을 셋업할때 MCP Server라고 부르지만~ 여기서 구성되는 전체그림에서는 Client이기때문에 Client로 표현했습니다. 
      • MCP Server : MCP Client에게 액터로 구성된 기능들을 제공합니다.
        • RavenDB : 도큐먼트 DB로 몽고DB와 유사하지만 AICD,풀텍스트검색,반경검색,벡터검색,그래프검색등 다양한 검색을 보편적으로 지원하며 MCP및 RAG 연구할때 AI연구 DB로 심플하게 이용할수 있습니다.  - RavenDB with Akka.net
        • RecoderActor : 노트를 작성합니다.
        • SearchActor : 노트검색기능을 제공합니다.
        • HistoryActor : 노트작성및 검색 이용기록을 인메모리로 보유해 요청하면 이력을 제공합니다.
      • MCP가 사용하는 기능이 확장되면 결국 MSA 구성으로 가거나 MSA화된 서비스를 이용하게 됩니다. 모놀리식으로  저장소를 구현하고 작동시킬수 있으며~ 필요하면 클러스터 구성으로 확장할수 있습니다.
        • Akka Cluster - 더 자세한 내용은 AkkaCluster 편을 참고


      코파일럿에서 여기서 구현된 MCP를 이용한 LLM샘플을 살펴보고 구현코드를 마지막으로 살펴보겠습니다.

      작성된 MCP TOOL

      Image Added

      • Agent LLM에서 Tool을 선택해 이용할수있습니다.
      • 노트는 보편적단어이니 구현된 컨텍스트 설명에 웹노리노트라고 명시해 프롬프트 작성시 웹노리 노트 작성해라고 하면 여기서 구현된 MCP툴이 작동됩니다.

      노트작성

      Image Added


      데이터 저장확인

      Image Added

      • LLM에 의해 데이터가 작 작성되었습니다.
      • RAG에 활용되는 임베딩기능을 여기서 소개는 제외되었지만~ 벡터검색 기능을 지원해 이용및 추가로 활용할수도 있습니다.


      키워드검색

      Image Added


      반경검색

      Image Added


      사용히스토리

      Image Added

      • 최근작성과 최근검색 히스토리를 제공합니다. - DB사용없이 인메모리


      MCP를 구현하는 대상은 모두 유닛테스트화가 되어 있으며

      RAG에 활용이 되는 벡터검색도 가능하며 임베딩된 벡터값의 유사도를 검색할수 있습니다.

      Code Block
      themeEmacs
          [Fact(DisplayName = "SearchNoteByVectorAreOk")]
          public void SearchNoteByVectorAreOk()
          {
              var actorSystem = _akkaService.GetActorSystem();
              
              TestProbe testProbe = this.CreateTestProbe(actorSystem);
              
              var searchActor = actorSystem.ActorOf(Props.Create(() => new SearchActor()));
              
              searchActor.Tell(testProbe.Ref);
      
              testProbe.ExpectMsg("done-ready");
      
              Within(TimeSpan.FromMilliseconds(3000), () =>
              {
                  searchActor.Tell(new SearchNoteByVectorCommand()
                  {
                      Vector = new float[] { 1.0f, 2.0f, 3.0f },
                      TopN = 10
                  });
                  
                  var result = testProbe.ExpectMsg<SearchNoteActorResult>();
                  
                  // Output results
                  foreach (var note in result.Notes)
                  {
                      output.WriteLine($"Title: {note.Title}, Content: {note.Content}, Category: {note.Category}" +
                                       $" Latitude: {note.Latitude}, Longitude: {note.Longitude}");
                  }
      
                  Assert.NotNull(result.Notes);
              });
          }


      노트저장소

      Code Block
      themeEmacs
      public class NoteRepository
      {
          private readonly IDocumentStore _store;
          
          
          public NoteRepository()
          {
              _store = new DocumentStore
              {
                  Urls = new[] { "http://localhost:9000" },
                  Database = "net-core-labs"
              };
              _store.Initialize();
              
              new NoteIndex().Execute(_store);
          }
          
          public void AddNote(NoteDocument note)
          {
              using (var session = _store.OpenSession())
              {
                  session.Store(note);
                  session.SaveChanges();
              }
          }
          
          public List<NoteDocument> SearchByText(string title,string content, string category)
          {
              using (var session = _store.OpenSession())
              {
                  // 명시적으로 변수로 선언
                  var titleValue = title;
                  var contentValue = content;
                  var categoryValue = category;
      
                  IRavenQueryable<NoteDocument> query = session.Query<NoteDocument>();
                  
                  if (!string.IsNullOrEmpty(titleValue))
                  {
                      query = query.Search(r => r.Title, titleValue);         // 제목 풀텍스트 검색
                  }
      
                  if (!string.IsNullOrEmpty(contentValue))
                  {
                      query = query.Search(r => r.Content, contentValue);     // 컨텐츠 풀텍스트 검색
                  }
      
                  if (!string.IsNullOrEmpty(categoryValue))
                  {
                      query = query.Where(r => r.Category == categoryValue); // 카테고리 필터
                  }
                  
                  var results = query.ToList();
                  
                  return results;
              }
          }
          
          public List<NoteDocument> SearchByRadius(double latitude, double longitude, double radiusKm)
          {
              using (var session = _store.OpenSession())
              {
                  return session.Query<NoteDocument>()
                      .Spatial(
                          r => r.Point(x => x.Latitude, x => x.Longitude),
                          criteria => criteria.WithinRadius(radiusKm, latitude, longitude))
                      .ToList();
              }
          }
          
          public List<NoteDocument> SearchByVector(float[] vector, int topN = 5)
          {
              using (var session = _store.OpenSession())
              {
                  var results = session.Query<NoteDocument>()
                      .VectorSearch(
                          field => field.WithEmbedding(x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single),
                          queryVector => queryVector.ByEmbedding(new RavenVector<float>(vector)),
                          0.85f,
                          topN)
                      .Customize(x => x.WaitForNonStaleResults())
                      .ToList();
      
                  return results;
              }
          }
          
      }
      • RavenDB가 고급검색 기능을 다양하게 지원해서~ 구현복잡도및 난이도를 낮추었습니다. 여러DB를 사용하게되는경우 ETL이 필요할수도 있는데 Zero ETL
      • DB개발은 지루하면 안된다는 모토를가진 모던DB로 다양한 검색기능응 이용할때 큰 불편함은 없었습니다. -다만 등장한지 얼마안되 최신기능은 GPT보다 문서레퍼런스를 참고하는게 좋음(원래 원칙이지만 요즘은 주의 사항이됨)

      검색저장소를 이용하는 SearchActor

      Code Block
      themeEmacs
      public class SearchActor : ReceiveActor
      {
          private readonly ILoggingAdapter _logger = Context.GetLogger();
      
          private IActorRef? testProbe;
          
          private readonly NoteRepository noteRepository;
          
          private IActorRef? historyActor;
          
          public SearchActor()
          {
              noteRepository = new NoteRepository();
              
              Receive<IActorRef>(actorRef =>
              {
                  testProbe = actorRef;
      
                  testProbe.Tell("done-ready");
              });
              
              Receive<SetHistoryActorCommand>(msg =>
              {
                  historyActor = msg.HistoryActor;
                  
                  if (testProbe != null)
                  {
                      testProbe.Tell("done-set-history");
                  }
              });
      
              Receive<SearchNoteByTextCommand>(command =>
              {
                  _logger.Info($"SearchNoteByTextCommand: {command.Title}, {command.Content}, {command.Category}");
                  
                  var notes = noteRepository.SearchByText(command.Title, command.Content, command.Category);
                  
                  _logger.Info($"SearchNoteByTextCommand: {notes.Count} notes found");
                  
                  Sender.Tell(new SearchNoteActorResult()
                  {
                      Notes = notes
                  });
                  
                  if (testProbe != null)
                  {
                      testProbe.Tell(new SearchNoteActorResult()
                      {
                          Notes = notes
                      });
                  }
                  
                  if(historyActor != null)
                  {
                      historyActor.Tell(notes);
                  }
                  
              });
              
              Receive<SearchNoteByRadiusActorCommand>(command =>
              {
                  var notes = noteRepository.SearchByRadius(command.Latitude, command.Longitude, command.Radius);
                  
                  Sender.Tell(new SearchNoteActorResult()
                  {
                      Notes = notes
                  });
                  
                  if (testProbe != null)
                  {
                      testProbe.Tell(new SearchNoteActorResult()
                      {
                          Notes = notes
                      });
                  }
                  
                  if(historyActor != null)
                  {
                      historyActor.Tell(notes);
                  }
                  
              });
              
              Receive<SearchNoteByVectorCommand>(command =>
              {
                  var notes = noteRepository.SearchByVector(command.Vector, command.TopN);
                  
                  Sender.Tell(new SearchNoteActorResult()
                  {
                      Notes = notes
                  });
                  
                  if (testProbe != null)
                  {
                      testProbe.Tell(new SearchNoteActorResult()
                      {
                          Notes = notes
                      });
                  }
                  
                  if(historyActor != null)
                  {
                      historyActor.Tell(notes);
                  }
              });
          }
      }
      • 액터모델을 이용한 이벤트 기반의 코드이며 서버기능을 수행할수 있습니다.  REST 인터페이스가 익숙하다면 액터모델을 걷어내고 RestAPI화해 교체할수 있는 영역의 코드입니다.
      • HistoryActor와 상호작용해~ 검색을 시도하면 검색 히스토리를 남깁니다.  히스토리 작성이 성공할때까지 대기하지 않고~ 성공여부도 관심사가 아니며 단지필요한 이벤트를 발생시킵니다. FireAndForgot 패턴의 일종입니다.
      • 이벤트 소싱등 CQRS패턴을 MCP내부 액터모델에 적용한다고하면  다음문서를참고해 능동적으로작동하는 액터모델을 이용  Agent코드를  작성할수도  있습니다.

      작성된 MCP Context

      Code Block
      themeEmacs
      [McpServerToolType]
      public static class NoteTool
      {
          [McpServerTool, Description("웹노리 노트에 노트를 추가합니다.")]
          public static async Task<string> AddNote(ActorService actorService, 
              [Description("노트의 제목으로 필수값입니다.")] string title,
              [Description("노트의 컨텐츠값으로 필수값입니다.")] string content,
              [Description("노트의 카테고리입니다.")] string? category, 
              [Description("노트에 위치정보가 있다면 latitude를 입력")] double? latitude, 
              [Description("노트에 위치정보가 있다면 longitude 입력")] double? longitude,
              [Description("노트에 임베딩된 값이 있다면 입력")] float[]? tagsEmbeddedAsSingle
              )
          {
              var note = new NoteDocument
              {
                  Title = title,
                  Category = category,
                  Content = content,
                  Latitude = latitude,
                  Longitude = longitude,
                  CreatedAt = DateTime.UtcNow,
                  TagsEmbeddedAsSingle = new RavenVector<float>(tagsEmbeddedAsSingle)
              };
              
              actorService.RecordActor.Tell(new AddNoteCommand()
              {
                  Title = title,
                  Category = category,
                  Content = content,
                  Latitude = latitude,
                  Longitude = longitude,
                  TagsEmbeddedAsSingle = new RavenVector<float>(tagsEmbeddedAsSingle)
              }, ActorRefs.NoSender);
              
              return JsonSerializer.Serialize(note);
          }
          
          [McpServerTool, Description("웹노리 노트에서 Text검색을 합니다. 최소 하나값이 필수입니다.")]
          public static async Task<string> SearchNoteByText(ActorService actorService, 
              [Description("웹노리 노트에서 타이틀을 키워드 검색합니다.")] string? title, 
              [Description("웹노리 노트에서 컨텐츠를 키워드 검색합니다.")] string? content, 
              [Description("웹노리 노트에서 카테고리를 키워드 검색합니다.")] string? category)
          {
              var result = await actorService.SearchActor.Ask(new SearchNoteByTextCommand()
              {
                  Title = title,
                  Content = content,
                  Category = category
              }, TimeSpan.FromSeconds(5));
              
              if (result is SearchNoteActorResult searchResult)
              {
                  return JsonSerializer.Serialize(searchResult.Notes);
              }
              
              return "Failed to get note history.";
          }
          
          [McpServerTool, Description("웹노리 노트에서 반경검색을 합니다. 모두 필수값입니다.")]
          public static async Task<string> SearchNoteByRadius(ActorService actorService, 
              [Description("This is the latitude of the note ")] double latitude, 
              [Description("This is the longitude of the note ")] double longitude, 
              [Description("This is the radius(m) of the note ")] double radius)
          {
              var result = await actorService.SearchActor.Ask(new SearchNoteByRadiusActorCommand()
              {
                  Latitude = latitude,
                  Longitude = longitude,
                  Radius = radius
              }, TimeSpan.FromSeconds(5));
              
              if (result is SearchNoteActorResult searchResult)
              {
                  return JsonSerializer.Serialize(searchResult.Notes);
              }
              
              return "Failed to get note history.";
          }
          
          [McpServerTool, Description("웹노리 노트에서 벡터검색을 합니다. 모두 필수값입니다.")]
          public static async Task<string> SearchNoteByVector(ActorService actorService, 
              [Description("This is the vector of the note  If there is no value, enter null.")] float[] vector, 
              [Description("This is the top N of the note  If there is no value, enter null.")] int topN)
          {
              var result = await actorService.SearchActor.Ask(new SearchNoteByVectorCommand()
              {
                  Vector = vector,
                  TopN = topN
              }, TimeSpan.FromSeconds(5));
              
              if (result is SearchNoteActorResult searchResult)
              {
                  return JsonSerializer.Serialize(searchResult.Notes);
              }
              
              return "Failed to get note history.";
          }
          
          
          [McpServerTool, Description("웹노리 노트에서 최근 추가된 노트를 가져옵니다.")]
          public static async Task<string> GetNoteHistory(ActorService actorService)
          {
              //SearchNoteActorResult
              var result = await actorService.HistoryActor.Ask(new GetNoteHistoryCommand(), TimeSpan.FromSeconds(5));
              
              if (result is SearchNoteActorResult searchResult)
              {
                  return JsonSerializer.Serialize(searchResult.Notes);
              }
              
              return "Failed to get note history.";
          }
          
          [McpServerTool, Description("웹노리 노트에서 최근 검색된 노트를 가져옵니다.")]
          public static async Task<string> GetNoteSearchHistory(ActorService actorService)
          {
              //SearchNoteActorResult
              var result = await actorService.HistoryActor.Ask(new GetNoteSearchHistoryCommand(), TimeSpan.FromSeconds(5));
              
              if (result is SearchNoteActorResult searchResult)
              {
                  return JsonSerializer.Serialize(searchResult.Notes);
              }
              
              return "Failed to get note history.";
          }
      
          
      }
      • Agent LLM이 툴을 선택할때 필요로하는 Description과 구현체를 정리하며 MCP Server에서 사실상 핵심코드영역입니다.
      • 작동코드는 모두 액터객체를 이용한 이벤트 방식을 사용합니다.
        • Tell : 응답값을 바로 알필요가 없을이 이벤트 전송시사용
        • Ask : 이벤트 전송후 해당 이벤트에 반응하는 응답값을 대기할때 사용


      클라이언트 서버모델

      Code Block
      themeEmacs
      public class ActorService
      {
          private readonly ActorSystem actorSystem;
          
          public IActorRef SearchActor { get; set; }
          
          public IActorRef RecordActor { get; set; }
          
          public IActorRef HistoryActor { get; set; }
          
          public ActorService(bool serverMode)
          {
              Console.WriteLine($"ActorService initialized in {(serverMode ? "Server" : "Client")} mode.");
      
              // HOCON 설정
              var config = ConfigurationFactory.ParseString($@"
                  akka {{
                      actor {{
                          provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
                      }}
                      remote {{
                          dot-netty.tcp {{
                              hostname = ""127.0.0.1""
                              port = {(serverMode ? 5500 : 0)} // 서버 모드일 때만 포트 5500 사용
                          }}
                      }}
                  }}
              ");
      
              actorSystem = ActorSystem.Create("MyActorSystem", config);
      
              if (serverMode)
              {
                  // 서버 모드일 때만 작동액터 생성 : MCP Server 
                  SearchActor = actorSystem.ActorOf<SearchActor>("search-actor");
                  RecordActor = actorSystem.ActorOf<RecordActor>("record-actor");
                  HistoryActor = actorSystem.ActorOf<HistoryActor>("history-actor");
      
                  RecordActor.Tell(new SetHistoryActorCommand()
                  {
                    "--project",
        HistoryActor = HistoryActor
                  "D:\\Code\\Webnori\\NetCoreLabs\\McpServer\\McpServer.csproj"});
      
                  SearchActor.Tell(new SetHistoryActorCommand()
        ]
                {
        }
                }
          }
      }

      Image Removed

      Image Removed

      Code Block
      themeEmacs
      2025-04-19 10:18:39.831 [warning] [server stderr] info: ModelContextProtocol.Protocol.Transport.StdioServerTransport[857250842]
      2025-04-19 10:18:39.832 [warning] [server stderr]       Server (stream) (McpServer) transport reading messages.
      2025-04-19 10:18:39.905 [warning] [server stderr] info: Microsoft.Hosting.Lifetime[0]
      2025-04-19 10:18:39.905 [warning] [server stderr] HistoryActor = HistoryActor
                  });
              }
              else
              {
            Application started. Press Ctrl+C to shut down.
      2025-04-19 10:18:39.906 [warning] [server stderr] info: Microsoft.Hosting.Lifetime[0]
      2025-04-19 10:18:39.906 [warning] [server stderr] // 클라이언트 모드일 때 원격 액터 참조 : MCP Client
              Hosting environment: Production
      2025-04-19 10:18:39.906 [warning] [server stderr] info: Microsoft.Hosting.Lifetime[0]
      2025-04-19 10:18:39.906 [warning] [server stderr] var remoteAddress = "akka.tcp://MyActorSystem@127.0.0.1:5500";
             Content root path: C:\Users\psmon
      2025-04-19 10:18:39.913 [warning] [serverSearchActor stderr] info: ModelContextProtocol.Server.McpServer[570385771]
      2025-04-19 10:18:39.914 [warning] [server stderr]= actorSystem.ActorSelection($"{remoteAddress}/user/search-actor")
                    Server (McpServer 1.0.0.0) method 'initialize' request handler called.
      2025-04-19 10:18:39.923 [warning] [server stderr] info: ModelContextProtocol.Server.McpServer[1867955179]
      2025-04-19 10:18:39.923 [warning] [server stderr]ResolveOne(TimeSpan.FromSeconds(3)).Result;
      
                  RecordActor = actorSystem.ActorSelection($"{remoteAddress}/user/record-actor")
                     Server .ResolveOne(McpServer 1.0.0.0), Client (Visual Studio Code 1.99.3) method 'initialize' request handler completed.
      2025-04-19 10:18:39.937 [warning] [server stderr] info: ModelContextProtocol.Server.McpServer[570385771]
      2025-04-19 10:18:39.938 [warning] [server stderr] TimeSpan.FromSeconds(3)).Result;
      
                  HistoryActor = actorSystem.ActorSelection($"{remoteAddress}/user/history-actor")
               Server (McpServer 1.0.0.0), Client (Visual Studio Code 1.99ResolveOne(TimeSpan.FromSeconds(3) method 'tools/list' request handler called.
      2025-04-19 10:18:39.938 [warning] [server stderr] info: ModelContextProtocol.Server.McpServer[570385771]
      2025-04-19 10:18:39.938 [warning] [server stderr]       Server (McpServer 1.0.0.0), Client (Visual Studio Code 1.99.3) method 'tools/call' request handler called.
      2025-04-19 10:18:39.944 [warning] [server stderr] info: ModelContextProtocol.Server.McpServer[1867955179]
      2025-04-19 10:18:39.944 [warning] [server stderr]       Server (McpServer 1.0.0.0), Client (Visual Studio Code 1.99.3) method 'tools/list' request handler completed.
      2025-04-19 10:18:39.944 [info] Discovered 2 tools
      2025-04-19 10:18:39.957 [warning] [server stderr] info: ModelContextProtocol.Server.McpServer[1867955179]
      2025-04-19 10:18:39.957 [warning] [server stderr]       Server (McpServer 1.0.0.0), Client (Visual Studio Code 1.99.3) method 'tools/call' request handler completed.).Result;
              }
          }
          
      }
      • LLM이 이용하는 것은 MCP Client이며 , Client는 LLM실행시마다 Context가 다시 초기화되기때문에~ 상태를 유지하는 Server 객체를 이용합니다.
      • 동일 작동코드에서 다른 지점은 이부분으로 Client는 Server가 생성한 액터모델을 Remote로 이용하게 됩니다.
        • 원격액터는 클러스터 배치및~ 액터 샤딩을 통한 멀티테넌스처리도 가능하게됩니다.  여기서는 싱글톤 객체와 유사하게 구성되었습니다.
      • 독립된 서버 기능을 이용하기위해 기능을 RestAPI Endpoint화 하고 이벤트를 내부 분산처리하는경우 Kafka를 추가로 이용할수 있겠지만 여기서는 AkakRemote Protocol을 이용해 그러한 기능들이 한방에 포함되었습니다.

      MCP Server

      Image Added

      • MCP에 대응하는 단위테스트를 먼저작성해 연결하는 바톰~업 방식을 이용하였으며 , 닷넷 메인 IDE에서는 서버 디버깅및 Server를 구동했습니다.


      MCP-Client

      Image Added

      • 동일저장소에서 clientMode 옵션을 주면 클라이언트 모드로 작동하며 MCP자체에 대응합니다. 


      닷넷에서 MCP구현시 확장가능 요소

      ML.NET

      Image Added


      Cluter 분산처리

      Image Added


      이상 닷넷이 제공하는 MCPContext 구현코드작성과  액터모델을 이용해 Client(Mcp,리모트액터호출)/Server Mode(액터기능제공) 로 분리작동해본 변종 실험이였습니다.

      전체코드 : https://github.com/psmon/NetCoreLabs/tree/main/McpServer