Delphi에서 Model Context Protocol (MCP) 서버 구축

· 컴포넌트

Model Context Protocol이란?

Model Context Protocol(MCP)은 AI 모델에 도구, 데이터 및 프롬프트 템플릿을 노출하기 위한 Anthropic의 개방형 표준이에요. AI용 USB로 생각하세요: 모든 MCP 서버는 모든 MCP 클라이언트(Claude Desktop, Cursor, Continue, 자체 Delphi 앱, OpenAI의 클라이언트)에 연결될 수 있으며 모델은 즉시 사용 가능한 도구와 호출 방법을 발견해요.

MCP 이전에는 모든 AI 통합이 각 개별 앱에 도구 정의, JSON 스키마 및 호출 디스패처를 연결하는 것을 의미했어요. MCP는 그것을 단일 프로토콜로 축소해요: 서버를 한 번 작성하면 모든 모델이 사용할 수 있어요. 이미 비즈니스 API 스택(ERP 쿼리, CRM 조회, 파일 생성기, 청구 함수)이 있는 Delphi 회사의 경우 MCP는 해당 API를 AI 액세스 가능하게 만드는 가장 저렴한 경로예요.

이 튜토리얼은 TsgcAI_MCP_Server 컴포넌트를 사용하여 Delphi에서 MCP 서버를 구축하고, 작동하는 get_weather 도구를 노출하고, stdio 및 HTTP 전송 모두를 통해 Claude Desktop에 연결하는 과정을 살펴봐요.

역사가 MCP를 맥락에 놓아요. Anthropic은 호환되지 않는 도구 호출 형식의 확산 — OpenAI의 함수 호출 JSON, Google의 Gemini 그라운딩 도구, 모든 공급업체의 맞춤형 스키마 — 에 대한 답변으로 2024년 말에 사양을 게시했어요. 이것을 읽을 때쯤이면 MCP 지원이 Claude Desktop, Cursor, Continue, Zed 및 대부분의 최신 AI IDE에 출시되고 있어요; OpenAI는 2026년 초에 호환성을 발표했어요. 다시 말해: 오늘 MCP 서버를 구축하는 것은 오늘과 내일 모두 고객이 사용할 수 있는 모든 클라이언트에 걸쳐 보상하는 일회성 투자예요.

MCP 빌딩 블록

MCP 서버는 세 가지 종류의 기능을 노출할 수 있어요:

도구

타입이 지정된 입력이 있는 호출 가능한 함수. 모델이 호출 시점을 결정해요. 예: get_weather(city), create_invoice(customer, lines).
리소스

모델이 가져올 수 있는 읽기 전용 데이터. 각 리소스에는 URI가 있어요. 예: file:///docs/handbook.md, db://customers/12345.
프롬프트

사용자(모델이 아님)가 선택하는 재사용 가능한 프롬프트 템플릿. 예: "공식 스페인어로 번역" 또는 "회의 노트 요약" 템플릿.

프로토콜은 두 가지 전송 중 하나를 통해 JSON-RPC 2.0을 사용해요: stdio(서버가 자식 프로세스; 메시지가 stdin/stdout을 통해 흐름) 또는 서버-투-클라이언트 스트림용 Server-Sent Events가 있는 HTTP. Stdio는 Claude Desktop과 같은 데스크톱 통합의 기본값이에요; HTTP는 서버가 다른 머신에서 실행되거나 여러 클라이언트를 제공할 때 올바른 선택이에요.

컴포넌트는 단일 API 뒤에 두 전송을 숨겨요. 도구 핸들러를 한 번 작성한 다음 시작 시 전송을 선택해요. 동일한 코드가 Claude Desktop 헬퍼, 네트워크 도달 가능한 HTTP 서비스 또는 한 프로세스 내에서 동시에 둘 다로 실행돼요. 단일 박스에서 30+ MCP 서버를 실행하는 회사를 보았어요. 다른 포트에 걸쳐 다중화되고 하나 또는 두 개의 stdio 바이너리, 모두 동일한 Delphi 코드베이스를 제공해요.

프로젝트 설정

uses 절에 sgcAI_MCP_Server를 추가하고, 폼에 TsgcAI_MCP_Server를 드롭하고(또는 콘솔 앱의 경우 코드에서 생성), 이벤트를 연결하고, 시작하세요. 컴포넌트가 JSON-RPC 프레이밍, 메시지 라우팅 및 기능 광고를 처리해요.

uses
  sgcAI_MCP_Server, sgcAI_MCP_Classes;

procedure TForm1.FormCreate(Sender: TObject);
begin
  oMCP := TsgcAI_MCP_Server.Create(Self);
  oMCP.ServerInfo.Name    := 'weather-mcp';
  oMCP.ServerInfo.Version := '1.0.0';

  // Wire handlers
  oMCP.OnListTools := DoListTools;
  oMCP.OnCallTool  := DoCallTool;

  // Stdio transport for Claude Desktop integration
  oMCP.Transport := mtStdio;
  oMCP.Active    := True;
end;

도구 구현: get_weather

도구에는 두 개의 핸들러가 필요해요. OnListTools는 어떤 도구가 존재하고 어떤 인수를 수락하는지 클라이언트에 알려줘요. OnCallTool은 실제로 호출을 실행해요. 두 핸들러 모두 일반 Delphi 이벤트예요 — 수동 JSON 구성 없음.

procedure TForm1.DoListTools(Sender: TObject;
  const aTools: TsgcAI_MCP_Tools);
var
  oTool: TsgcAI_MCP_Tool;
begin
  oTool := aTools.Add;
  oTool.Name        := 'get_weather';
  oTool.Description := 'Return the current weather for a city.';
  oTool.InputSchema :=
    '{"type":"object",' +
     '"properties":{' +
       '"city":{"type":"string","description":"City name, e.g. Madrid"},' +
       '"units":{"type":"string","enum":["metric","imperial"],"default":"metric"}' +
     '},' +
     '"required":["city"]}';
end;

procedure TForm1.DoCallTool(Sender: TObject;
  const aRequest : TsgcAI_MCP_ToolCall;
  const aResult  : TsgcAI_MCP_ToolResult);
var
  vCity, vUnits, vReport: string;
begin
  if aRequest.Name = 'get_weather' then
  begin
    vCity  := aRequest.Arguments.S['city'];
    vUnits := aRequest.Arguments.S['units'];
    if vUnits = '' then vUnits := 'metric';

    // Call your own weather backend
    vReport := MyWeatherService.Fetch(vCity, vUnits);
    aResult.AddText(vReport);
  end
  else
    aResult.Error('Unknown tool: ' + aRequest.Name);
end;

그것이 전체 구현이에요. 컴포넌트는 스키마에 대한 입력 검증, 오류 형식 지정 및 기본 JSON-RPC 엔벨로프를 처리해요. 비즈니스 로직에 집중하세요.

좋은 도구 설계를 위한 몇 가지 원칙, 힘들게 배운 것:

리소스 노출

도구는 작업용이고; 리소스는 모델이 읽을 수 있는 데이터용이에요. 리소스 핸들러는 주어진 URI에 대한 콘텐츠를 반환해요. 문서 서버는 docs/ 아래의 모든 Markdown 파일을 리소스로 노출할 수 있어요.

oMCP.OnListResources := DoListResources;
oMCP.OnReadResource  := DoReadResource;

procedure TForm1.DoListResources(Sender: TObject;
  const aResources: TsgcAI_MCP_Resources);
var
  vFiles: TStringDynArray;
  i     : Integer;
  oRes  : TsgcAI_MCP_Resource;
begin
  vFiles := TDirectory.GetFiles('C:\docs', '*.md');
  for i := 0 to High(vFiles) do
  begin
    oRes := aResources.Add;
    oRes.URI         := 'file:///' + StringReplace(vFiles[i], '\', '/', [rfReplaceAll]);
    oRes.Name        := ExtractFileName(vFiles[i]);
    oRes.MimeType    := 'text/markdown';
    oRes.Description := 'Documentation page';
  end;
end;

procedure TForm1.DoReadResource(Sender: TObject;
  const aURI: string; const aResult: TsgcAI_MCP_ResourceResult);
var
  vPath: string;
begin
  vPath := StringReplace(Copy(aURI, 9, MaxInt), '/', '\', [rfReplaceAll]);
  if FileExists(vPath) then
    aResult.AddText(TFile.ReadAllText(vPath))
  else
    aResult.Error('Resource not found');
end;

Stdio vs HTTP 전송

서버가 실행되는 위치와 사용자에 따라 전송을 선택하세요.

전송 사용 사례 장점 단점
Stdio 로컬 데스크톱 도구 (Claude Desktop, Cursor) 구성 없음, 포트 없음, OS가 프로세스 라이프사이클 처리 프로세스당 하나의 클라이언트, 원격 액세스 없음
HTTP / SSE 공유 기업 서버, 웹 클라이언트, 다중 테넌트 많은 클라이언트, 네트워크 도달 가능, TLS, 인증 호스팅, 포트 관리, 인증 디자인 필요

전송 전환은 한 줄짜리예요:

// Stdio (default for desktop)
oMCP.Transport := mtStdio;

// HTTP listener on port 8080
oMCP.Transport       := mtHTTP;
oMCP.HTTPOptions.Host := '0.0.0.0';
oMCP.HTTPOptions.Port := 8080;
oMCP.HTTPOptions.TLS.Enabled  := True;
oMCP.HTTPOptions.TLS.CertFile := 'cert.pem';
oMCP.HTTPOptions.TLS.KeyFile  := 'key.pem';
oMCP.Active := True;

Claude Desktop에서 연결

stdio의 경우 claude_desktop_config.json을 편집하고 컴파일된 .exe를 가리키는 항목을 추가하세요. Claude Desktop이 요청에 따라 프로세스를 생성해요.

{
  "mcpServers": {
    "weather": {
      "command": "C:\\Tools\\weather-mcp.exe",
      "args": []
    }
  }
}

Claude Desktop을 다시 시작하면 프롬프트 영역에 작은 도구 아이콘이 표시돼요. "도쿄의 날씨는 어떤가요?"라고 물으면 Claude가 Delphi 도구를 호출해요.

HTTP의 경우 MCP 가능 클라이언트를 https://your-host:8080/mcp로 가리키세요. 동일한 Delphi 서버가 Claude Desktop 인스턴스, Cursor 세션 및 사용자 정의 Delphi MCP 클라이언트를 동시에 행복하게 처리해요.

프롬프트: 잘 사용되지 않는 세 번째 기능

도구와 리소스가 모든 관심을 받아요; 프롬프트는 조용한 일꾼이에요. MCP 프롬프트는 사용자가 MCP 클라이언트의 UI 메뉴에서 호출하는 재사용 가능한 템플릿 — "이 회의 요약", "액션 항목 추출", "공식 스페인어로 번역" — 이에요. 그런 다음 모델은 사용자가 제공한 모든 컨텍스트에 대해 해당 프롬프트를 실행해요.

내부 도구의 경우 이것은 금이에요. 지원 팀은 Claude Desktop 내에서 회사 승인 프롬프트의 큐레이션된 목록을 받아요. 영업 팀은 톤을 이미 알고 있는 "후속 이메일 초안" 프롬프트를 받아요. 그들 중 누구도 프롬프트 엔지니어링을 배울 필요가 없어요 — 그저 템플릿을 선택해요.

oMCP.OnListPrompts := DoListPrompts;
oMCP.OnGetPrompt   := DoGetPrompt;

procedure TForm1.DoListPrompts(Sender: TObject;
  const aPrompts: TsgcAI_MCP_Prompts);
var
  oPrompt: TsgcAI_MCP_Prompt;
begin
  oPrompt := aPrompts.Add;
  oPrompt.Name        := 'summarise_meeting';
  oPrompt.Description := 'Turn a meeting transcript into bullet decisions and actions.';
  oPrompt.AddArgument('transcript', 'Plain-text transcript', True);
end;

procedure TForm1.DoGetPrompt(Sender: TObject;
  const aRequest: TsgcAI_MCP_PromptRequest;
  const aResult : TsgcAI_MCP_PromptResult);
begin
  if aRequest.Name = 'summarise_meeting' then
    aResult.AddMessage('user',
      'You are a meeting note taker. Read the transcript and produce: ' +
      '(1) a 3-sentence summary, (2) decisions taken, (3) action items ' +
      'with owners and due dates.' + sLineBreak + sLineBreak +
      aRequest.Arguments.S['transcript']);
end;

프로덕션 고려 사항

MCP 서버를 프로덕션에 배포하기 전에 예산을 잡아야 할 몇 가지 사항:

디버깅 팁

MCP는 AI 클라이언트 내부에서 보이지 않게 실행되므로 처음 무언가 잘못되면 디버깅이 까다로워요. 세 가지 습관이 시간을 절약해 줄 거예요:

마무리

MCP는 클라이언트별 연결 없이 모든 최신 AI 클라이언트가 사용할 수 있는 것으로 내부 Delphi API를 변환해요. TsgcAI_MCP_Server 컴포넌트는 JSON-RPC 프레이밍, 전송 및 기능 광고를 처리하여 실제 도구 본문을 작성하도록 남겨요. 하나의 읽기 전용 도구로 시작하여 모델이 어떻게 사용하는지 보고 쓰기, 리소스 및 프롬프트로 확장하세요.

내부적으로 MCP를 출시하면서 얻은 가장 큰 교훈: 가치는 "Claude가 이제 우리 API를 사용할 수 있다"가 아니에요. 가치는 "회사의 누구나 API를 배우지 않고도 자연어를 통해 API를 사용할 수 있다"예요. 그것은 누가 무엇을 할 수 있는지 바꾸고, 그것이 실제 생산성 향상이 오는 곳이에요.

매칭 클라이언트를 원하나요? 모든 MCP 서버를 소비하는 Delphi 앱을 빌드하는 TsgcAI_MCP_Client를 참조하세요. 그리고 AI 컴포넌트가 처음이라면 시작하기 허브가 5분 안에 설치를 안내해 줄 거예요.