MCP(모델 컨텍스트 프로토콜) 도구

MCP(모델 컨텍스트 프로토콜) 도구

여러 서비스의 도구를 각자의 방식으로 연동하다 보면 통합 비용이 커져요. AI SDK는 MCP(Model Context Protocol) 서버에 연결해 그 도구·리소스·프롬프트를 표준화된 인터페이스로 사용할 수 있게 지원해요. 이 글에서는 MCP 클라이언트를 만들고, 전송 방식을 고르고, 도구를 AI SDK에 연결하는 흐름을 정리해 볼게요.

출처: 공식문서

본문

MCP 클라이언트 초기화

@ai-sdk/mcpcreateMCPClient로 클라이언트를 만들어요. 전송 방식은 세 가지를 고를 수 있고, 프로덕션 배포에는 HTTP 전송을 권장해요. stdio는 로컬 서버 전용이라 배포 환경에 쓸 수 없어요. AI SDK MCP 클라이언트는 레거시 초기화 기반 프로토콜 버전과 무상태 MCP 2026-07-28을 모두 지원해요.

HTTP 전송 (권장) — 클라이언트에 직접 설정할 수 있어요. 헤더나 OAuth authProvider, SSRF 방지를 위한 redirect(기본 'error') 옵션을 줄 수 있어요.

import { createMCPClient } from '@ai-sdk/mcp';

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
    // optional: configure HTTP headers
    headers: { Authorization: 'Bearer my-api-key' },
    // optional: provide an OAuth client provider for automatic authorization
    authProvider: myOAuthClientProvider,
    // optional: allow redirect responses (default is 'error' to prevent SSRF)
    redirect: 'follow',
  },
});

레거시 MCP 서버가 Streamable HTTP 세션을 쓰면 저장한 세션에 다시 붙을 수 있어요. initialSessionId, initialProtocolVersion, initialInitializeResult로 복원하고 onSessionIdChange/onSessionExpired로 세션 변화를 추적할 수 있어요. initialInitializeResult가 주어지면 캐시된 init 메타데이터를 재사용해 initialize 요청을 다시 보내지 않아요. terminateSessionOnClose: false로 로컬 클라이언트만 닫고 나중에 다시 붙을 수 있게 둘 수도 있어요.

SSE 전송type: 'sse'url로 HTTP 기반 대체 전송을 쓸 수 있고, 역시 헤더·OAuth를 지원해요.

Stdio 전송 (로컬 서버)StdioClientTransportcommandargs를 지정해 로컬 MCP 서버를 띄워요.

커스텀 전송 — 표준 전송이 다루지 못하는 요구가 있다면 MCPTransport 인터페이스를 구현해 직접 제공할 수도 있어요. 참고로 createMCPClient가 돌려주는 클라이언트는 도구 변환 용도의 가벼운 클라이언트라, 자동 세션 영속성이나 재개 가능한 스트림, 알림 수신 같은 전체 MCP 클라이언트 기능은 전부 지원하지 않아요.

OAuth 보안

HTTP/SSE 전송에서 authProvider를 제공하면 OAuth 인증을 지원해요. 서버 측 앱에서 내가 통제하지 않는 MCP 서버에 연결한다면, validateAuthorizationServerURL을 구현해 신뢰하는 인가 서버 오리진만 허용할 수 있어요. 이 훅은 SDK가 인가 서버 메타데이터를 가져오기 전에 호출되므로, 거부된 URL은 요청되지 않아요.

OAuth 콜백을 마칠 때는 콜백의 iss 파라미터를 authcallbackIssuer로 넘겨요. iss가 있으면 발견된 인가 서버 발급자와 정확히 일치해야 하며, 일치하지 않으면 인가 코드는 교환되지 않아요.

일시적 도구 실패 재시도

MCP 도구 호출은 rate limit이나 일시적 과부하, 게이트웨이 타임아웃 같은 일시적 전송 문제로 실패할 수 있어요. 이때 tools/call 요청의 자동 재시도를 켜려면 클라이언트 생성 시 maxRetries를 넘기면 돼요.

const mcpClient = await createMCPClient({
  transport: {
    type: 'http',
    url: 'https://your-server.com/mcp',
  },
  maxRetries: 2,
});

재시도는 기본적으로 꺼져 있어요. 내장 재시도 매칭은 일시적 HTTP·네트워크 실패만 대상으로 하고, 잘못된 도구 인자 같은 JSON-RPC 애플리케이션 오류나 isError: true인 성공 응답은 재시도 없이 즉시 표면화돼요. 멱등하지 않은 도구(이메일 발송이나 레코드 생성 같은)는 재시도하면 부수 효과가 중복될 수 있으니, 재시도가 안전한 도구에만 켜야 해요.

클라이언트 닫기

초기화 후에는 사용 방식에 맞춰 클라이언트를 닫아야 해요. 짧은 사용(단일 요청)은 응답이 끝나면, 장기 실행(CLI 앱)은 애플리케이션 종료 시 닫는 식이에요. 스트리밍 응답에서는 streamTextonEnd 콜백 안에서 mcpClient.close()를 호출하면 돼요.

MCP 도구 사용

클라이언트의 tools() 메서드는 MCP 도구와 AI SDK 도구 사이의 어댑터 역할을 해요. 두 가지 접근이 있어요.

스키마 탐색(schema discovery)await mcpClient.tools() 하면 서버가 제공하는 모든 도구를 자동으로 나열하고 서버 스키마로 입력 타입을 추론해요. 구현은 단순하고 서버 변경과 자동으로 동기화되지만, 개발 중 TypeScript 타입 안전성이 없고 서버의 모든 도구가 로드돼요.

스키마 정의(schema definition)schemas로 도구와 입력 스키마를 명시적으로 정의해요. 전체 타입 안전성과 IDE 자동완성을 얻고, 정의한 도구만 불러와 필요한 도구에 집중할 수 있어요. 도구가 제공하는 structuredContent를 받고 싶다면 outputSchema를 정의해 타입 있는 도구 결과를 얻을 수도 있어요. outputSchema가 있으면 클라이언트가 결과에서 structuredContent를 추출해 런타임에 스키마로 검증하고, 타입 안전성을 제공해요. 서버가 structuredContent를 안 주면 텍스트 콘텐츠에서 JSON을 파싱하는 방식으로 폴백하고, 둘 다 없거나 검증이 실패하면 에러를 던져요. outputSchema가 없으면 원시 CallToolResult 객체를 돌려줘요.

도구 어노테이션과 승인

MCP 서버는 readOnlyHint, destructiveHint, idempotentHint, openWorldHint 같은 어노테이션으로 도구 동작을 설명할 수 있어요. 이 어노테이션은 각 도구의 metadata.annotations와 도구 호출의 toolMetadata.annotations에 노출돼요. 다만 어노테이션은 신뢰할 수 없는 서버 제공 힌트라서 MCP 클라이언트가 자동으로 승인 정책으로 바꾸지 않아요. 애플리케이션은 도구 허용 목록, 범위가 제한된 자격 증명, 자체 toolApproval 정책 같은 결정적 제어와 결합해 써야 해요. 예컨대 서버가 명시적으로 읽기 전용으로 표시한 도구만 자동 실행하고 나머지는 사용자 승인을 요구하는 보수적 정책을 toolApproval 콜백으로 구현할 수 있어요.

리소스·완성·프롬프트

  • 리소스 — 도구(모델이 제어)와 달리 애플리케이션이 컨텍스트로 가져올 시점을 결정해요. listResources(), readResource({ uri }), listResourceTemplates()로 활용할 수 있어요.
  • 완성(completions) — 서버가 completions 기능을 광고하면 complete로 부분 인자 값에 대한 자동완성 제안을 받을 수 있어요. 서버가 capabilities.completions를 광고하지 않으면 MCPClientError를 던져요.
  • 프롬프트 — 실험적 기능이에요. experimental_listPrompts()로 나열하고 experimental_getPrompt({ name, arguments })로 인자와 함께 가져올 수 있어요.

도구 정의 드리프트(“rug pull”) 감지

MCP 서버는 앱이 처음 연결할 때 도구 정의(이름·설명·입력 스키마)를 보내고 보통 그 시점에 검토·승인해요. 그런데 프로토콜은 서버가 나중에 같은 도구 이름에 다른 정의(주입된 지시문이 담긴 설명이나 필드가 늘어난 스키마)를 서빙하는 것을 막지 못해요. SDK는 호출마다 전달되는 도구를 그대로 쓰므로, 나중에 바뀐 정의가 승인된 것과 비교 없이 쓰일 수 있는 게 MCP의 "rug pull" 공격 유형이에요.

AI SDK는 fingerprintToolsdetectToolDrift 두 함수로 승인된 정의를 고정하고 변경을 감지해요. fingerprintTools는 각 도구의 보안 관련 필드(문자열 설명, 해석된 입력 스키마, 제목)를 도구 이름→다이제스트 맵으로 만든 뒤, detectToolDrift로 두 맵을 비교해요. 기준선 저장과 드리프트에 대한 대응(차단·재승인·경고)은 애플리케이션의 몫이에요. 이는 설명·스키마·제목의 변형(프롬프트 주입·스키마 확장 벡터)을 감지하지만, 이름·설명·스키마가 모두 그대로인 동작/엔드포인트 교체는 감지하지 못해요 — 도구가 MCP 서버에서 원격 실행되므로 그 변경은 클라이언트에 보이지 않거든요.

더 알아보기