Post

MCP(Model Context Protocol)

1. MCP(Model Context Protocol)

Image

MCP는 2024년 11월에 앤트로픽에서 발표하였으며 애플리케이션이 LLM에 컨텍스트를 제공하는 방식을 표준화하는 개방형 프로토콜입니다. 앤트로픽은 MCP가 AI의 ‘USB-C’ 포트와 같다고 설명했습니다. USB-C가 다양한 주변 기기 및 액세서리에 기기를 연결하는 표준화된 방식을 제공하는 것처럼, MCP는 AI 모델을 다양한 데이터 소스 및 도구에 연결하는 표준화된 방식을 제공합니다.


MCP 사용 이유

  • LLM을 기반으로 에이전트와 복잡한 워크플로를 구축하는 데 도움을 줍니다.
  • LLM은 데이터 및 도구와 통합해야 하는 경우가 많으며, 직접 연결할 수 있는 미리 구축된 통합 목록이 점점 늘어나고 있는 상황에서 유용할 것으로 보인다.




2. Core Architecture

MCP는 본질적으로 호스트 애플리케이션이 여러 서버에 연결할 수 있는 클라이언트-서버 아키텍처를 따릅니다.

Image

  • MCP 호스트 : MCP를 통해 데이터에 액세스하려는 Claude Desktop, IDE 또는 AI 도구와 같은 프로그램
  • MCP 클라이언트 : 서버와 1:1 연결을 유지하는 프로토콜 클라이언트
  • MCP 서버 : 표준화된 모델 컨텍스트 프로토콜을 통해 각각 특정 기능을 노출하는 경량 프로그램
  • 로컬 데이터 소스 : MCP 서버가 안전하게 액세스할 수 있는 컴퓨터의 파일, 데이터베이스 및 서비스
  • 원격 서비스 : MCP 서버가 연결할 수 있는 인터넷(예: API를 통해)을 통해 사용 가능한 외부 시스템


2-1. 개요

Image

  • 호스트: 연결을 시작하는 LLM 애플리케이션(예: Claude Desktop 또는 IDE)입니다.
  • 클라이언트: 호스트 애플리케이션 내부에서 서버와 1:1 연결을 유지합니다.
  • 서버: 클라이언트에게 컨텍스트, 도구 및 프롬프트를 제공합니다.


2-2. 핵심 구성 요소

프로토콜 계층

프로토콜 계층은 메시지 프레이밍, 요청/응답 연결, 고수준 통신 패턴을 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Protocol<Request, Notification, Result> {
    // Handle incoming requests
    setRequestHandler<T>(schema: T, handler: (request: T, extra: RequestHandlerExtra) => Promise<Result>): void

    // Handle incoming notifications
    setNotificationHandler<T>(schema: T, handler: (notification: T) => Promise<void>): void

    // Send requests and await responses
    request<T>(request: Request, schema: T, options?: RequestOptions): Promise<T>

    // Send one-way notifications
    notification(notification: Notification): Promise<void>
}


전송 계층

전송 계층은 클라이언트와 서버 간의 실제 통신을 처리합니다.


전송 메커니즘

  • Stdio 전송
    • 통신을 위해 표준 입출력을 사용합니다.
    • 로컬 프로세스에 이상적
  • SSE 전송을 통한 HTTP
    • 서버-클라이언트 메시지에 대해 서버에서 보낸 이벤트를 사용합니다.
    • 클라이언트-서버 메시지에 대한 HTTP POST
  • 모든 전송은 JSON-RPC 2.0을 사용하여 메시지를 교환


메시지 유형

  1. 요청: Request
  2. 결과: Result
  3. 오류: Error
  4. 알림: Notification


2-3. 연결 수명주기

초기화

Image

  1. initialize클라이언트는 프로토콜 버전 및 기능과 함께 요청을 보냅니다 .
  2. 서버는 프로토콜 버전과 기능으로 응답합니다.
  3. initialized클라이언트가 확인으로 알림을 보냅니다 .
  4. 정상적인 메시지 교환이 시작됩니다.

메시지 교환

  • 요청-응답 : 클라이언트 또는 서버가 요청을 보내고 다른 쪽이 응답합니다.
  • 알림 : 양측이 일방적으로 메시지를 보냅니다.

종료

어느 당사자든 연결을 종료할 수 있습니다.




3. Resources

리소스는 MCP의 핵심 요소로, 서버가 클라이언트가 읽고 LLM 상호 작용에 대한 컨텍스트로 사용할 수 있는 데이터와 콘텐츠를 노출할 수 있도록 해줍니다.


리소스 종류

  • 파일 내용
  • 데이터베이스 레코드
  • API 응답
  • 스크린샷 및 이미지


리소스 URI

리소스는 다음 형식을 따르는 URI를 사용하여 식별됩니다.

프로토콜과 경로 구조는 MCP 서버 구현에 의해 정의됩니다. 서버는 자체적인 사용자 지정 URI 체계를 정의할 수 있습니다.


리소스 유형

  • 텍스트 리소스: UTF-8로 인코딩된 텍스트 데이터(ex: 소스 코드, JSON/XML 데이터)
  • 바이너리 리소스: base64로 인코딩된 원시 바이너리 데이터(ex: 이미지, PDF, 오디오 파일, 비디오 파일)


클라이언트-사용 가능한 리소스 찾기

  • Direct resources:서버는 resources/list 엔드포인트를 통해 구체적인 리소스 목록을 제공합니다
  • Resource templates: 동적 리소스의 경우 서버는 클라이언트가 유효한 리소스 URI를 구성하는 데 사용할 수 있는 URI 템플릿을 노출할 수 있습니다.


클라이언트-리소스 읽기

리소스를 읽으려면 클라이언트가 resources/read리소스 URI로 요청을 합니다.

{
  contents: [
    {
      uri: string;        // The URI of the resource
      mimeType?: string;  // Optional MIME type

      // One of:
      text?: string;      // For text resources
      blob?: string;      // For binary resources (base64 encoded)
    }
  ]
}


Flow

  1. 클라이언트 → 서버에 “리소스 목록 주세요” 요청
  2. 서버 → file:///logs/app.log 이라는 리소스 목록 응답
  3. 클라이언트 → “그 로그 파일 읽어주세요” 요청
  4. 서버 → 실제 파일 내용 읽어서 반환




4. Prompts

프롬프트를 사용하면 서버가 재사용 가능한 프롬프트 템플릿과 워크플로를 정의하여 클라이언트가 사용자와 LLM에 쉽게 제공할 수 있습니다.


MCP 프롬프트 템플릿

  • 동적 인수 허용
  • 리소스의 컨텍스트 포함
  • 여러 상호 작용 연결
  • 특정 워크플로 가이드
  • UI 요소(슬래시 명령 등)로 표면화
  • 동적 프롬프트 생성 가능


클라이언트-프롬프트 찾기

클라이언트는 prompts/list 엔드포인트를 통해 사용 가능한 프롬프트를 찾을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Request
{
  method: "prompts/list"
}

// Response
{
  prompts: [
    {
      name: "analyze-code",
      description: "Analyze code for potential improvements",
      arguments: [
        {
          name: "language",
          description: "Programming language",
          required: true
        }
      ]
    }
  ]
}

클라이언트-프롬프트 사용

프롬프트를 사용하려면 클라이언트가 prompts/get요청합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Request
{
  method: "prompts/get",
  params: {
    name: "analyze-code",
    arguments: {
      language: "python"
    }
  }
}

// Response
{
  description: "Analyze Python code for potential improvements",
  messages: [
    {
      role: "user",
      content: {
        type: "text",
        text: "Please analyze the following Python code for potential improvements:\n\n```python\ndef calculate_sum(numbers):\n    total = 0\n    for num in numbers:\n        total = total + num\n    return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n```"
      }
    }
  ]
}




5. Tools

도구는 MCP의 강력한 기본 요소로, 서버가 클라이언트에 실행 가능한 기능을 노출할 수 있도록 합니다. 도구를 통해 LLM은 외부 시스템과 상호 작용하고, 계산을 수행하고, 실제 환경에서 작업을 수행할 수 있습니다.


도구의 주요 기능

  • 발견 : 클라이언트는 tools/list엔드포인트를 통해 사용 가능한 도구를 나열할 수 있습니다.
  • 호출 : 도구는 tools/call서버가 요청된 작업을 수행하고 결과를 반환하는 엔드포인트를 사용하여 호출됩니다.
  • 유연성 : 도구는 간단한 계산부터 복잡한 API 상호 작용까지 다양합니다.

오류 처리 도구 오류는 MCP 프로토콜 수준 오류가 아닌 결과 객체 내에 보고되어야 합니다. 이를 통해 LLM이 오류를 확인하고 잠재적으로 처리할 수 있습니다.

  1. 결과 isError에 설정true
  2. content배열 에 오류 세부 정보 포함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
  // Tool operation
  const result = performOperation();
  return {
    content: [
      {
        type: "text",
        text: `Operation successful: ${result}`
      }
    ]
  };
} catch (error) {
  return {
    isError: true,
    content: [
      {
        type: "text",
        text: `Error: ${error.message}`
      }
    ]
  };
}

MCP 서버에 도구 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const server = new Server({
  name: "example-server",
  version: "1.0.0"
}, {
  capabilities: {
    tools: {}
  }
});

// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [{
      name: "calculate_sum",
      description: "Add two numbers together",
      inputSchema: {
        type: "object",
        properties: {
          a: { type: "number" },
          b: { type: "number" }
        },
        required: ["a", "b"]
      }
    }]
  };
});

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "calculate_sum") {
    const { a, b } = request.params.arguments;
    return {
      content: [
        {
          type: "text",
          text: String(a + b)
        }
      ]
    };
  }
  throw new Error("Tool not found");
});




6. Sampling

서버가 LLM으로부터 응답 생성을 요청할 수 있도록 지원합니다. 보안과 개인 정보 보호를 유지하면서 정교한 에이전트 동작을 가능하게 합니다.


샘플링 작동 방식

  • sampling/createMessage서버가 클라이언트에게 요청을 보냅니다.
  • 클라이언트는 요청을 검토하고 수정할 수 있습니다.
  • LLM의 클라이언트 샘플
  • 클라이언트가 완료를 검토합니다
  • 클라이언트는 결과를 서버로 반환합니다.

요청 매개변수

messages: LLM으로 전송할 대화 기록이 포함

  • role: “사용자” 또는 “보조자”
  • content: 메시지 내용은 다음과 같습니다.
  • text: 필드 가 있는 텍스트 콘텐츠
  • data: base64 및 mimeType필드가 포함된 이미지 콘텐츠

modelPreferences: 서버가 모델 선택 기본 설정을 지정

  • hints: 클라이언트가 적절한 모델을 선택하는 데 사용할 수 있는 다양한 모델 이름 제안:
    • name: 전체 또는 일부 모델 이름과 일치할 수 있는 문자열(예: “claude-3”, “sonnet”)
  • costPriority: 비용 최소화의 중요성
  • speedPriority: 저지연 응답의 중요성
  • intelligencePriority: 고급 모델 기능의 중요성

systemPrompt: 서버는 특정 시스템 프롬프트를 요청

includeContext: 포함할 MCP 컨텍스트 지정

  • “none”: 추가 컨텍스트 없음
  • “thisServer”: 요청 서버의 컨텍스트를 포함합니다.
  • “allServers”: 연결된 모든 MCP 서버의 컨텍스트 포함

샘플링 매개변수: LLM 샘플링을 미세 조정

  • temperature: 무작위성 제어(0.0~1.0)
  • maxTokens: 생성할 최대 토큰
  • stopSequences: 생성을 중지하는 시퀀스 배열
  • metadata: 추가 공급자별 매개변수

요청 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "What files are in the current directory?"
        }
      }
    ],
    "systemPrompt": "You are a helpful file system assistant.",
    "includeContext": "thisServer",
    "maxTokens": 100
  }
}

응답 예시

1
2
3
4
5
6
7
8
9
10
11
{
  model: string,  // Name of the model used
  stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string,
  role: "user" | "assistant",
  content: {
    type: "text" | "image",
    text?: string,
    data?: string,
    mimeType?: string
  }
}




7. Transports

Transports는 클라이언트와 서버 간 통신의 기반을 제공합니다. 전송은 메시지를 송수신하는 기본적인 메커니즘을 처리합니다. MCP는 JSON-RPC 2.0을 와이어 형식으로 사용합니다. 전송 계층은 MCP 프로토콜 메시지를 전송을 위해 JSON-RPC 형식으로 변환하고, 수신된 JSON-RPC 메시지를 다시 MCP 프로토콜 메시지로 변환하는 역할을 합니다.


JSON-RPC 메시지의 3가지 유형

요청

1
2
3
4
5
6
{
  jsonrpc: "2.0",
  id: number | string,
  method: string,
  params?: object
}

응답

1
2
3
4
5
6
7
8
9
10
{
  jsonrpc: "2.0",
  id: number | string,
  result?: object,
  error?: {
    code: number,
    message: string,
    data?: unknown
  }
}

알림

1
2
3
4
5
{
  jsonrpc: "2.0",
  method: string,
  params?: object
}


전송 유형

표준 입출력(stdio): 표준 입출력 스트림을 통한 통신

1
2
3
4
5
6
7
8
9
const server = new Server({
  name: "example-server",
  version: "1.0.0"
}, {
  capabilities: {}
});

const transport = new StdioServerTransport();
await server.connect(transport);


서버 전송 이벤트(SSE): 클라이언트-서버 통신을 위한 HTTP POST 요청을 통해 서버-클라이언트 스트리밍을 가능

SSE 전송은 적절하게 보안되지 않으면 DNS 리바인딩 공격에 취약할 수 있습니다.

DNS 리바인딩 공격 예방

  1. 예상 소스에서 왔는지 확인하기 위해 들어오는 SSE 연결에서 항상 Origin 헤더를 검증합니다.
  2. 로컬로 실행할 때 서버를 모든 네트워크 인터페이스 (0.0.0.0)에 바인딩하지 마세요. 대신 로컬호스트(127.0.0.1)에만 바인딩하세요.
  3. 모든 SSE 연결에 대해 적절한 인증을 구현합니다.

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import express from "express";

const app = express();

const server = new Server({
  name: "example-server",
  version: "1.0.0"
}, {
  capabilities: {}
});

let transport: SSEServerTransport | null = null;

app.get("/sse", (req, res) => {
  transport = new SSEServerTransport("/messages", res);
  server.connect(transport);
});

app.post("/messages", (req, res) => {
  if (transport) {
    transport.handlePostMessage(req, res);
  }
});

app.listen(3000);




This post is licensed under CC BY 4.0 by the author.