byteforce

CPN 한국어 자습서 · 외부 문서 한국어 미러

MCP 문서 · Develop

클라이언트 모범 사례

Client Best Practices · 원문: modelcontextprotocol.io/docs/develop/clients/client-best-practices

아래는 원문을 한국어로 옮긴 미러입니다. 코드·명령은 원문 그대로이며, 가장 최신 정보는 하단 원문 링크에서 확인하세요.

많은 서버와 도구에 걸쳐 MCP 호스트 애플리케이션을 확장하기 위한 패턴

에이전트와 같은 MCP 호스트 애플리케이션이 더 많은 MCP 서버에 연결하고 수백 또는 수천 개의 도구에 대한 접근 권한을 축적함에 따라, 도구 관리에 대한 단순한 접근 방식은 한계에 부딪힙니다. 모든 도구 정의를 모델의 컨텍스트 창에 미리 로드하면 토큰이 낭비되고 지연이 증가하며 모델 성능이 저하됩니다. 순차적 도구 호출 사이에 대용량 중간 결과를 모델을 통해 전달하면 문제가 더 심각해집니다.

두 가지 패턴이 이러한 문제를 해결합니다. 점진적 탐색은 도구 정의가 컨텍스트에 진입하는 시점을 제어하고, 프로그래매틱 도구 호출은 도구가 호출되는 방법을 제어합니다.

점진적 도구 탐색

단순한 MCP 호스트 구현은 각 대화 시작 시 연결된 모든 서버의 도구 정의를 모델에 직접 전달합니다. 도구가 몇 개에 불과하다면 이는 충분히 합리적입니다. 그러나 호스트가 수백 개의 도구를 노출하는 수십 개의 서버에 접근할 때, 모델이 사용자 메시지를 읽기도 전에 도구 정의만으로 컨텍스트 창의 대부분을 소비할 수 있습니다.

점진적 탐색은 이를 방지합니다.

점진적 탐색 사용 시점

점진적 탐색은 도구 정의가 컨텍스트 창의 많은 부분을 차지할 때 가장 적합합니다. 도구 정의가 컨텍스트 창의 작은 부분만 차지하는 소수의 도구의 경우, 모든 도구를 로드해도 괜찮습니다. 도구 정의가 사용 가능한 컨텍스트 창의 상당 부분을 차지하게 되면 클라이언트는 점진적 탐색으로 전환해야 합니다. 전환 시점을 결정하기 위한 임계값을 다음과 같이 구현하는 것을 권장합니다.

탐색 전략 선택

모델이 search_tools 도구를 호출하면 검색 전략을 선택해야 합니다.

일부 모델 제공업체는 내장 도구 검색을 제공합니다. 예를 들어, OpenAIAnthropic은 이를 네이티브로 지원합니다. 제공업체 문서에서 동등한 기능을 확인하세요. 사용 가능한 경우, 직접 구현보다 플랫폼의 도구 검색을 선호할 수 있습니다. 제공업체가 이를 제공하지 않거나 특수한 검색 로직(예: 도메인별 순위나 접근 제어 필터링)이 필요한 경우 직접 구현하세요.

아래의 3계층 패턴은 사용자 정의 검색 기반 방식을 상세히 설명하지만, 계층화된 원칙(카탈로그, 검사, 실행)은 검색 메커니즘에 상관없이 적용됩니다.

점진적 탐색 사용하기

점진적 탐색의 일반적인 구현으로 검색 기반 3계층 방식이 있습니다.

1계층: 카탈로그. 호스트는 사용 가능한 기능을 검색하기 위한 소수의 메타 도구를 노출합니다. search_tools 도구는 자연어 쿼리를 받아 도구 이름과 간략한 설명을 포함한 일치 항목을 반환합니다.

코드 · 명령
// The model calls a lightweight search tool
search_tools({ query: "update salesforce record" })

// Returns concise matches: names and one-line descriptions only
→ [
    { name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
    { name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
  ]

2계층: 검사. 모델이 후보를 식별하면 해당 도구에 대한 전체 정의(입력 스키마, 출력 스키마, 문서)만 가져옵니다.

코드 · 명령
// The model inspects only the tool it needs
get_tool_details({ name: "salesforce_updateRecord" });

이는 단일 도구의 완전한 스키마를 반환합니다.

코드 · 명령
{
  "name": "salesforce_updateRecord",
  "description": "Updates a record in Salesforce",
  "inputSchema": {
    "type": "object",
    "properties": {
      "objectType": {
        "type": "string",
        "description": "Salesforce object type"
      },
      "recordId": { "type": "string", "description": "Record ID to update" },
      "data": { "type": "object", "description": "Fields to update" }
    },
    "required": ["objectType", "recordId", "data"]
  }
}

3계층: 실행. 모델은 인터페이스를 완전히 파악한 상태에서 필요한 정의만 로드하여 도구를 호출합니다.

이 패턴은 토큰 사용량을 크게 줄이고 도구 선택 정확도를 향상시킬 수 있습니다. 모델은 수백 개의 관련 없는 도구를 스캔하는 대신 관련성 있는 소수의 도구에 집중합니다. 다른 탐색 전략(임베딩, 서브에이전트 등)도 동일한 계층화 원칙을 따르지만 카탈로그 계층에서 다른 검색 메커니즘을 사용합니다.

동적 서버 관리

점진적 탐색은 개별 도구를 넘어 전체 서버까지 확장됩니다. 시작 시 구성된 모든 서버에 연결하는 대신, 호스트는 다음을 수행할 수 있습니다.

  1. 사용 가능한 서버와 고수준 설명의 레지스트리를 유지합니다.
  2. 모델이 해당 서버의 기능이 필요하다고 판단할 때만 서버에 연결합니다.
  3. 현재 작업과 더 이상 관련 없는 서버를 연결 해제하여 컨텍스트를 확보합니다.
코드 · 명령
sequenceDiagram
    participant Model
    participant Host
    participant Registry
    participant Server

    Model->>Host: search_available_servers("CRM")
    Host->>Registry: Query available servers
    Registry-->>Host: Salesforce server (not connected)
    Host-->>Model: Salesforce server available

    Model->>Host: enable_server("salesforce")
    Host->>Server: Initialize connection
    Server-->>Host: Server capabilities + tools
    Host-->>Model: Salesforce server connected

    Note over Model: Task complete

    Model->>Host: disable_server("salesforce")
    Host->>Server: Close connection
    Host-->>Model: Server disconnected, context freed

이는 사용자의 의도를 미리 알 수 없는 범용 에이전트에 특히 효과적입니다. 에이전트는 항상 켜져 있는 최소한의 서버 세트로 시작하고 필요에 따라 다른 서버에 연결합니다. 에이전트 스킬과 결합하면, 스킬 파일이 필요한 MCP 서버를 선언할 수 있으며 호스트는 해당 스킬이 호출될 때만 서버에 연결합니다.

구현 지침

점진적 탐색을 구현할 때:

지침 이유
여러 세부 수준 제공 모델이 이름만, 이름과 설명, 또는 전체 스키마 응답 중에서 선택할 수 있도록 합니다.
도구 정의 캐싱 서버에서 가져온 후 호스트 측에서 정의를 메모이제이션하여 나중에 재주입할 때 tools/list 왕복이 필요하지 않게 합니다. 이는 현재 모델 컨텍스트에 있는 것과는 별개입니다.
list_changed 시 갱신 서버가 notifications/tools/list_changed를 보낼 때 검색 카탈로그를 다시 인덱싱합니다.
서버별 도구 그룹화 모델이 관련 기능에 대해 추론할 수 있도록 도구를 소스 서버별로 구성하여 표시합니다.

프롬프트 캐싱과의 상호작용

대부분의 제공업체는 tools 배열을 포함한 프롬프트 접두어를 캐시합니다. 대화 중에 도구 정의를 추가하거나 제거하면 해당 캐시가 무효화되고, 그로 인한 캐시 미스는 제거한 정의보다 더 많은 토큰을 소비할 수 있습니다. 캐싱을 유지하려면:

프로그래매틱 도구 호출 / 코드 모드

직접 도구 호출에서는 모든 도구 호출이 왕복입니다. 모델이 도구 호출을 생성하고, 클라이언트가 실행하고, 전체 결과가 모델 컨텍스트로 돌아옵니다. 작업에 여러 도구를 체이닝해야 하는 경우(문서 읽기, 변환, 다른 곳에 쓰기), 각 중간 결과가 모델을 통과하며, 모델이 처리할 것이 없어도 토큰을 소비하고 지연을 추가합니다.

프로그래매틱 도구 호출(흔히 "코드 모드"라고도 함)은 클라이언트가 도구 호출을 효과적으로 구성할 수 있는 방법을 제공합니다. 도구를 직접 호출하는 대신, 모델이 도구를 호출하는 코드를 작성합니다. 코드는 샌드박스 환경에서 실행되고, 최종 결과만 모델로 돌아옵니다.

프로그래매틱 도구 호출은 강력하고 MCP 도구와 리소스를 더 효율적으로 사용할 수 있게 하지만, 클라이언트가 샌드박스 환경을 구현해야 합니다.

작동 방식

호스트는 MCP 도구 스키마를 샌드박스 내에서 사용 가능한 타입이 지정된 API로 변환합니다. 모델이 도구가 필요하면 스크립트를 작성하여 실행합니다.

1단계: MCP 스키마에서 프로그래매틱 API 생성. 호스트는 각 서버의 도구 정의를 읽고, 각 도구의 인수와 outputSchema를 기반으로 타입이 지정된 함수를 생성합니다.

코드 · 명령
// Auto-generated from the Logging MCP server's tool schema
interface LogEntry {
  timestamp: string;
  message: string;
  level: string;
}

function logging_getLogs(input: {
  level: "error" | "warn" | "info";
  since: number;
}): Promise<{ entries: LogEntry[] }> {
  return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
}

// Auto-generated from the Ticketing MCP server's tool schema
function ticketing_createIssue(input: {
  title: string;
  body?: string;
  priority: "low" | "medium" | "high";
}): Promise<{ issueId: string }> {
  return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
}

MCP 서버는 각 도구에 대한 선택적 outputSchema를 제공할 수 있습니다. 출력 스키마가 있으면 호스트는 정확한 반환 타입(위의 LogEntry처럼)을 생성할 수 있습니다.

출력 스키마가 없을 때는 다음과 같이 처리합니다.

2단계: 모델이 이 API에 대한 코드를 작성합니다. 전체 결과가 컨텍스트를 통해 흐르는 별도의 도구 호출을 수행하는 대신, 모델은 단일 스크립트를 작성합니다. "지난 한 시간의 모든 오류 로그를 찾아 각 고유 오류에 대한 티켓을 제출"하는 작업을 생각해보세요. 직접 도구 호출로는 수천 개의 로그 항목이 모델 컨텍스트를 통과합니다. 코드 모드에서는 모델이 샌드박스에서 필터링합니다.

코드 · 명령
// Model-generated code, executes in sandbox
const logs = await logging_getLogs({
  level: "error",
  since: Date.now() - 3600000,
});

// Filter and deduplicate inside the sandbox, not in the model's context
const uniqueErrors = new Map<string, LogEntry>();
for (const log of logs.entries) {
  if (!uniqueErrors.has(log.message)) {
    uniqueErrors.set(log.message, log);
  }
}

for (const [message, log] of uniqueErrors) {
  await ticketing_createIssue({
    title: `Error: ${message}`,
    body: `First seen: ${log.timestamp}\nOccurrences: ${
      logs.entries.filter((l) => l.message === message).length
    }`,
    priority: "high",
  });
}

console.log(
  `Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
);

3단계: 샌드박스가 코드를 실행합니다. 샌드박스 내의 함수 호출은 인터셉트되어 호스트 브로커를 통해 적절한 MCP 서버로 라우팅됩니다. 로그 데이터와 티켓 생성은 모델 컨텍스트를 거치지 않고 서버 간에 직접 흐릅니다. console.log 출력, 즉 단일 요약 줄만 모델로 돌아옵니다.

샌드박스 선택

적절한 샌드박스는 모델이 작성하길 원하는 언어, 호스트 애플리케이션의 언어, 그리고 필요한 격리 수준에 따라 다릅니다. 다음 표는 추천이 아닌 예시 런타임을 나열합니다. 사용 사례에 맞는 성숙도를 평가하세요.

샌드박스 언어 런타임 / 라이브러리 호스트 언어 접근 방식
JavaScript Deno, isolated-vm Rust / Node / CLI 세밀한 권한을 가진 V8 기반 런타임. 완전한 잠금을 위해 모든 권한 비활성화 가능.
Python Monty (실험적) Rust AI 사용 사례를 위해 구축된 최소한의 Python 인터프리터. 기본적으로 I/O 없음.
TypeScript pctx (초기 단계) Python / Rust 저수준 Rust 지원으로 라이브러리로서 코드 모드 개념을 통합합니다.
모든 언어(Wasm) Wasmtime Rust / C / Go 모든 언어를 Wasm으로 컴파일하고 기능 기반 보안으로 실행합니다.

샌드박스에 관계없이 통합 패턴은 동일합니다. 호스트가 함수 스텁을 주입하고, 인프로세스 또는 stdio 채널을 통해 호출을 인터셉트하여(네트워크 권한을 완전히 거부할 수 있음) MCP 서버에 대한 tools/call 요청으로 디스패치합니다.

실행 아키텍처

구현에는 세 가지 구성 요소가 있습니다.

코드 · 명령
flowchart LR
    subgraph Host["MCP Host"]
        A[LLM] -->|writes code| B[Sandbox]
        B -->|function call| C[MCP Client]
        C -->|return value| B
        B -->|console output| A
    end
    C -->|tool call| D[MCP Server A]
    C -->|tool call| E[MCP Server B]
    D -->|result| C
    E -->|result| C

샌드박스는 직접 네트워크 접근 없이 격리된 환경에서 모델 생성 코드를 실행합니다. 외부 세계와의 유일한 인터페이스는 생성된 함수 스텁으로, 호출을 호스트로 다시 라우팅합니다.

호스트는 브로커 역할을 합니다. 샌드박스로부터 함수 호출을 받아 올바른 MCP 서버에 매핑하고, 도구 호출을 실행하고, 결과를 샌드박스로 반환합니다. 인증 토큰과 자격 증명은 호스트가 보유하며 생성된 코드에 절대 노출되지 않습니다.

모델은 샌드박스가 반환하는 것, 일반적으로 console.log 문의 출력이나 최종 반환 값만 봅니다. 이를 통해 모델(과 클라이언트 개발자)은 컨텍스트 창에 진입하는 것을 정확하게 제어할 수 있습니다.

보안 고려사항

프로그래매틱 도구 호출은 신중한 샌드박싱이 필요한 코드 실행 표면을 도입합니다.

오류 처리

MCP 도구 오류는 전송 실패가 아닌 isError: true가 있는 성공적인 응답으로 도착합니다. 생성된 래퍼는 이를 던져진 예외로 변환하여 모델이 작성한 코드가 try/catch를 사용할 수 있게 해야 합니다. 잡히지 않은 오류가 스크립트를 종료하면 모델이 자가 수정할 수 있도록 이를 스크립트의 결과로 표시합니다. 모델은 이미 커밋된 부분 부작용을 보고할 책임이 있습니다.

두 패턴의 결합

점진적 탐색과 프로그래매틱 도구 호출은 함께 잘 작동합니다. 모델은 탐색 도구를 사용하여 필요한 도구를 식별하고, 해당 스키마를 로드한 다음, 하나의 실행 패스에서 여러 도구를 호출하는 단일 스크립트를 작성합니다. 이 조합은 도구 정의의 토큰 비용과 도구 결과의 토큰 비용을 모두 최소화하여, 모델 컨텍스트가 데이터 전달이 아닌 추론에 집중하게 합니다.

원문(영어): https://modelcontextprotocol.io/docs/develop/clients/client-best-practices · 본 문서는 학습용 한국어 번역이며 원본의 권리는 원저작자(Model Context Protocol)에게 있습니다.

원문(영어): https://modelcontextprotocol.io/docs/develop/clients/client-best-practices