2장. 도구와 MCP

학습 목표

  • 에이전트가 오용하기 어렵고 토큰을 아끼는 도구를 설계하고, 개선 효과를 평가로 확인한다.
  • 도구 결과의 크기를 페이지 나누기·필터·잘라내기·참조로 관리한다.
  • MCP의 호스트·클라이언트·서버 역할과 2026-07-28 사양의 무상태 모델을 설명한다.
  • 프로토콜 오류와 도구 실행 오류를 구분하고, 회복 행동으로 이어지는 오류를 설계한다.
  • 도구 주석, 인증, scope를 신뢰 경계에 맞게 다루고 최소 권한을 부여한다.

들어가며

새로 입사한 사람에게 사내 시스템 매뉴얼 300쪽을 건네고 "필요한 건 여기 다 있어요"라고 말하는 장면을 떠올려 보세요. 정보는 빠짐없지만 첫 주 내내 엉뚱한 화면을 열고, 비슷한 이름의 메뉴를 헷갈리고, 오류 코드 앞에서 멈출 겁니다. 에이전트가 기존 API를 그대로 감싼 도구 수십 개를 받았을 때 겪는 일이 이와 같습니다.

도구는 에이전트가 세상을 보는 창이자 손입니다. 이 장은 먼저 어떤 도구를 만들고 이름·매개변수·결과·오류를 어떻게 설계할지 다룹니다. 이어서 MCP의 구조와 2026-07-28 사양의 변화, 주석·인증·scope로 권한 경계를 긋는 법을 정리하고, 깊이 읽기에서 도구가 수백 개로 늘 때의 전략과 코드로 도구를 부르는 방식을 봅니다.

핵심 개념

도구는 에이전트를 위한 인터페이스다

사람이 쓰는 화면을 설계하는 일을 HCI(사람-컴퓨터 인터페이스)라고 하듯, 에이전트가 쓰는 도구와 그 설명을 설계하는 일을 Anthropic은 ACI(에이전트-컴퓨터 인터페이스)라고 부르고, HCI만큼 공을 들이라고 권합니다. 같은 글은 SWE-bench 에이전트를 만들 때 전체 프롬프트보다 도구 최적화에 더 많은 시간을 썼다고 적습니다.

대표 기법이 포카요케(poka-yoke), 즉 실수하기 어렵게 만드는 설계입니다. 모델이 루트 디렉터리를 벗어난 뒤 상대 경로를 자꾸 틀리자, 팀은 도구가 항상 절대 경로만 받게 바꿨고 모델은 그 방식을 문제없이 썼습니다. 지시문에 "경로를 조심하라"고 쓰는 대신, 틀릴 수 있는 입력 자체를 없앤 것입니다. SIM 카드의 한쪽 모서리를 잘라 거꾸로 끼울 수 없게 만든 것과 같은 발상입니다.

점검 질문은 단순합니다. 설명과 매개변수만 보고 사용법이 분명한가, 사용 예와 경계 사례가 있는가, 신입 개발자에게 줘도 헷갈리지 않는가. 그리고 많은 입력으로 시험해 모델이 어디서 틀리는지 봅니다.

알맞은 도구 고르기

Anthropic의 "Writing effective tools for agents"는 도구가 많다고 결과가 좋아지지 않는다고 강조합니다. 흔한 실수는 API 엔드포인트를 하나씩 그대로 감싸는 것입니다. 주소록이라면 전체를 돌려주는 list_contacts 대신 search_contacts나 message_contact를, 일정이라면 빈 시간을 찾고 예약까지 하는 schedule_event 하나를 만드는 식으로, 영향이 큰 작업 흐름에 맞춘 소수의 도구로 여러 단계를 묶으라고 권합니다.

경계의 기준은 "사람 담당자라면 이 일을 한 번에 처리하는가"입니다. 너무 잘게 나누면 조합 부담으로 호출이 늘고, 너무 넓게 묶으면 매개변수가 많아져 잘못 부르기 쉽습니다. 겹치는 도구는 합치고, 비슷해 보이지만 용도가 다른 도구는 이름과 설명에서 차이를 드러냅니다.

이름, 설명, 매개변수

여러 서비스의 도구를 함께 쓰면 이름이 선택의 첫 단서가 됩니다. 같은 글은 서비스나 리소스 단위 접두사로 묶는 네임스페이스를 권합니다(asana_search, jira_search, asana_projects_search). 매개변수는 이름만으로 뜻이 분명해야 합니다. user보다 user_id가 낫습니다.

설명은 도구가 할 일과 하지 않을 일, 입력의 형식과 단위, 결과의 뜻을 적습니다. "레코드를 수정한다"는 설명에 id와 value만 받는 update 도구를 다음처럼 고쳐 보면 차이가 보입니다.

{
  "name": "shipment_set_weight",
  "description": "출고 전 화물의 실측 무게를 기록한다. 출고된 화물은 바꿀 수 없다. 무게는 킬로그램, 소수 첫째 자리까지.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "shipment_number": { "type": "string", "description": "예: SH-2026-004211" },
      "weight_kg": { "type": "number", "minimum": 0.1, "maximum": 30000 }
    },
    "required": ["shipment_number", "weight_kg"],
    "additionalProperties": false
  }
}

개선안은 단위를 매개변수 이름에 넣고, 허용 범위를 스키마로 막고, 바꿀 수 없는 경우를 설명에 밝혔습니다. 시간대처럼 모델이 잘 틀리는 값은 ISO 8601 형식과 시간대 표기를 강제하거나 UTC로만 받게 하는 식으로, 틀릴 여지를 형식에서 없앱니다. 스키마가 막지 못하는 업무 규칙(재고보다 많이 출고할 수 없다 등)은 서버에서 검사합니다. 형식이 맞는 호출도 업무상 틀릴 수 있기 때문입니다.

의미 있는 결과를 돌려준다

도구 결과는 다음 행동의 재료입니다. 같은 글은 uuid, 256px_image_url, mime_type 같은 저수준 식별자보다 이름처럼 에이전트가 바로 쓸 수 있는 필드를 돌려주라고 하며, 알아보기 어려운 식별자를 읽을 수 있는 말로 바꾸자 검색 정확도가 올랐다고 적습니다. 다음 호출에 꼭 ID가 필요하다면 이름과 함께 주면 됩니다.

응답 상세도를 고르게 하는 방법도 있습니다. response_format 같은 열거형 매개변수로 "concise"와 "detailed"를 고르게 하면, 대개는 간결한 응답을 쓰고 ID가 필요한 경우만 상세 응답을 고를 수 있습니다. 같은 글의 예에서 상세 응답은 206토큰, 간결한 응답은 72토큰으로 약 3분의 1이었습니다.

도구 결과 크기 관리

결과가 클수록 컨텍스트를 차지하고, 3장에서 볼 컨텍스트 rot을 앞당깁니다. 같은 글이 권하는 기법은 페이지 나누기, 범위 선택, 필터링, 합리적 기본값을 둔 잘라내기이며, Claude Code는 도구 응답을 기본 25,000토큰으로 제한한다고 적습니다.

기법 설계 예
기본 개수와 페이지 limit 기본 20, 다음 페이지 커서 반환
서버 측 필터 기간·상태·담당자로 먼저 좁히게 하는 매개변수
필드 선택 필요한 열만 고르는 fields 매개변수
잘라내기 안내 "결과 1,240건 중 20건만 표시. 기간을 좁히거나 status로 거르세요"
참조 반환 큰 산출물은 파일이나 리소스로 두고 위치만 돌려준다

잘라낼 때는 반드시 잘렸다는 사실과 범위를 결과에 적어야 합니다. 그렇지 않으면 에이전트는 받은 20건이 전부라고 믿습니다. MCP 도구는 결과에 resource_link로 나중에 읽을 URI를 돌려줄 수 있고, outputSchema를 정의하면 structuredContent로 구조화 결과를 냅니다. 출력 스키마가 있으면 서버는 반드시 그에 맞는 결과를 내야 하고(MUST) 클라이언트는 검증해야 하며(SHOULD), 하위 호환을 위해 같은 JSON을 텍스트 블록으로도 넣도록 권합니다.

오류는 회복 지시다

MCP 사양은 도구 오류를 두 갈래로 나눕니다.

종류 예 전달 방식 모델의 회복 가능성
프로토콜 오류 모르는 도구, 형식이 잘못된 요청, 서버 오류 JSON-RPC error 낮다
도구 실행 오류 API 실패, 입력 검증 실패, 업무 규칙 위반 결과의 isError: true와 설명 높다. 인자를 고쳐 다시 시도

사양은 클라이언트가 도구 실행 오류를 모델에 전달해 스스로 바로잡게 하라고(SHOULD) 적습니다. 실행 오류는 정상 결과의 모양으로 돌아옵니다. 2026-07-28 판의 모든 결과에는 결과 종류를 알리는 resultType이 붙고, 완료된 결과는 "complete"입니다.

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resultType": "complete",
    "content": [{ "type": "text", "text": "배송일은 오늘 이후여야 합니다. 오늘은 2026-10-20입니다." }],
    "isError": true
  }
}

좋은 오류 메시지는 다음 행동을 알려 줍니다. 입력을 고치면 되는지, 기다렸다 다시 하면 되는지, 권한을 더 받아야 하는지, 사람에게 넘겨야 하는지를 구분하세요. 주의할 경우가 하나 있습니다. 타임아웃처럼 실행 여부를 알 수 없는 오류입니다. 결제 요청이 처리된 뒤 응답만 사라졌을 수 있으므로, 이때는 "다시 시도"가 아니라 "처리 결과를 조회하라"가 올바른 지시입니다. 내부 스택 트레이스나 민감한 값은 오류에 담지 않습니다.

부작용이 있는 도구: 미리보기와 확정

조회 도구는 잘못 불러도 토큰만 잃지만, 변경 도구는 외부 시스템의 상태를 바꿉니다. 되돌리기 어려운 변경에는 미리보기와 확정의 분리가 효과적입니다.

  1. 준비 도구가 변경 대상, 바뀔 값, 영향(금액, 수신자, 건수)을 계산해 돌려주고, 짧은 유효기간의 미리보기 식별자를 발급한다.
  2. 사람이나 정책 검사가 미리보기 내용을 확인한다.
  3. 확정 도구는 미리보기 식별자만 받는다. 서버는 그 식별자가 가리키는 내용 그대로 실행한다.
  4. 같은 식별자로 두 번 확정해도 효과는 한 번만 일어나게 한다(멱등성).

이 구조는 승인한 내용과 실행한 내용이 달라지는 틈을 없애고, 재시도가 중복 실행으로 번지는 것도 막습니다. 상대값("10% 올려라")보다 절댓값("하루 예산을 30만 원으로")을 받게 하는 것도 같은 포카요케입니다. 같은 요청이 두 번 와도 결과가 같기 때문입니다.

MCP의 구성

MCP(Model Context Protocol)는 LLM 애플리케이션과 외부 데이터·도구를 잇는 개방형 프로토콜입니다. 메시지는 JSON-RPC 2.0 형식을 따르고, 참여자는 셋입니다.

역할 설명
호스트 연결을 시작하는 LLM 애플리케이션
클라이언트 호스트 안에서 서버 하나와 통신하는 커넥터
서버 맥락과 기능을 제공하는 서비스

서버는 세 기능을 제공합니다. 리소스(사용자나 모델이 쓸 맥락과 데이터), 프롬프트(사용자용 템플릿 메시지와 워크플로), 도구(모델이 실행하는 함수)입니다. 클라이언트는 서버가 사용자에게 추가 정보를 요청하는 elicitation을 제공할 수 있습니다. 2026-07-28 판은 Roots, Sampling, Logging을 폐기 예정(Deprecated)으로 지정했습니다. 당분간 동작하지만 새 구현은 도입하지 말라는 뜻입니다. 사양이 제시하는 대안은 각각 도구 매개변수나 서버 설정으로 디렉터리 넘기기, LLM 제공자 API와 직접 연동하기, 표준 오류나 OpenTelemetry로 로그 남기기입니다.

사양의 안전 원칙에 따르면 도구는 임의 코드 실행으로 다뤄야 하고, 호스트는 도구를 호출하기 전에 사용자의 명시적 동의를 받아야 하며, 사람이 호출을 거부할 수 있어야 합니다(SHOULD).

2026-07-28 사양: 무상태와 핸들

이 판의 가장 큰 변화는 MCP가 무상태(stateless) 프로토콜이 된 것입니다. 처리에 필요한 정보는 모두 요청 안에 들어 있고, 서버는 같은 연결의 이전 요청에서 프로토콜 버전·클라이언트 능력·신원 같은 맥락을 끌어와서는 안 됩니다(MUST NOT). 그래서 모든 클라이언트 요청은 _meta에 io.modelcontextprotocol/protocolVersion과 io.modelcontextprotocol/clientCapabilities를 반드시 담고, 빠지면 서버는 잘못된 매개변수 오류(-32602)로 거절합니다. 클라이언트 이름과 버전(io.modelcontextprotocol/clientInfo)도 넣도록 권하지만, 자기 신고 값이라 보안 판단에 쓰면 안 됩니다. 이전 판의 initialize 핸드셰이크와 세션 ID 헤더는 없어졌고, 서버가 지원 버전과 능력을 알리는 server/discover가 생겼습니다. 열린 stdio 프로세스도 대화나 세션이 아닙니다.

여러 호출에 걸친 상태(장바구니, 열린 브라우저, DB 트랜잭션)가 필요하면 생성 도구가 명시적 핸들을 돌려주고, 이후 호출이 그 핸들을 인자로 받습니다. 사양의 비규범 지침은 다음과 같습니다.

  • 인증된 서버에서 핸들은 이름일 뿐 권한이 아니다. 매 호출마다 호출자의 권한을 핸들과 대조한다.
  • 핸들은 내부 구조를 드러내지 않는 불투명 식별자로 만들고, 보존 기간은 생성 도구 설명에 적는다.
  • 만료되었거나 모르는 핸들에는 그 사실을 알리는 도구 실행 오류를 돌려줘 모델이 새로 만들게 한다.

처리 중간에 사용자 입력이 더 필요하면 다중 왕복 요청(MRTR)을 씁니다. 서버가 resultType: "input_required" 결과에 필요한 요청과 불투명한 requestState를 담아 돌려주면, 클라이언트는 응답을 채워 원래 요청을 새 ID로 다시 보냅니다. requestState는 클라이언트를 거쳐 오므로 서버는 이를 조작될 수 있는 입력으로 다루고, 권한이나 업무 논리에 영향을 주면 무결성을 보호해야 합니다(MUST). elicitation에는 클라이언트 화면의 입력 양식으로 받는 폼 모드와 사용자를 외부 페이지로 보내는 URL 모드가 있습니다. 폼 모드로는 비밀번호·API 키·토큰·결제 정보를 요청해서는 안 되고, 이런 정보는 클라이언트를 거치지 않는 URL 모드로 받습니다.

주의

프로토콜이 무상태라는 말은 서버가 업무 데이터나 장기 작업 상태를 갖지 않는다는 뜻이 아닙니다. 상태는 있어도 되지만, 연결이 아니라 명시적 식별자에 묶어야 한다는 뜻입니다. 2025-11-25까지의 판은 initialize 핸드셰이크를 쓰고 Streamable HTTP 서버가 세션 ID를 발급할 수도 있었으므로, 연결하는 상대의 버전을 먼저 확인하세요.

전송, 취소, 장기 작업

MCP의 표준 전송은 두 가지입니다. 클라이언트가 서버를 하위 프로세스로 띄워 표준 입출력으로 통신하는 stdio, 그리고 원격 서버와 HTTP로 통신하는 Streamable HTTP입니다. 이전의 HTTP+SSE 전송은 폐기 예정입니다.

취소와 타임아웃은 전송마다 다릅니다. Streamable HTTP에서는 요청의 응답 스트림을 닫는 것이 곧 취소이고, 서버는 클라이언트 연결이 끊기면 그 요청을 취소로 다뤄야 합니다. 2026-07-28 판은 스트림 재개 기능을 없앴으므로, 끊긴 요청은 새 ID로 다시 보내야 합니다. stdio에서는 notifications/cancelled를 보냅니다. 사양은 모든 요청에 타임아웃을 두라고 권하며, 진행 알림(progress)을 받을 때 타임아웃을 다시 잴 수는 있지만 진행 알림과 상관없이 최대 타임아웃은 늘 강제하라고(SHOULD) 덧붙입니다.

몇 분, 몇 시간 걸리는 작업을 연결 하나에 붙잡아 두면 중간 프록시의 타임아웃이나 클라이언트 재시작에 취약합니다. 공식 확장인 Tasks를 쓰면 서버가 결과 대신 내구성 있는 작업 핸들을 돌려주고, 클라이언트는 tasks/get으로 상태(working, input_required, completed, failed, cancelled)를 조회하며 재시작 뒤에도 같은 ID로 이어 갑니다. 확장은 양쪽이 모두 지원을 밝혀야 쓸 수 있습니다.

도구 주석과 신뢰

도구 정의에는 name, title, description, inputSchema, outputSchema, annotations 등이 들어갑니다. 이름은 1~128자, 영문자·숫자·밑줄·하이픈·점만 쓰도록 권합니다. 여러 서버의 도구를 모으면 이름이 겹칠 수 있으므로, 사양은 서버 식별자 접두사 같은 구분 전략을 권하고 serverInfo의 서버 이름은 고유하다는 보장이 없으니 그것에 기대지 말라고 합니다.

주석(annotations)은 도구의 동작을 설명하는 힌트입니다.

주석 뜻 기본값
readOnlyHint 환경을 바꾸지 않는다 false
destructiveHint 파괴적 변경을 할 수 있다(읽기 전용이 아닐 때만 의미) true
idempotentHint 같은 인자로 반복 호출해도 추가 효과가 없다(읽기 전용이 아닐 때만 의미) false
openWorldHint 외부의 열린 세계(웹 검색 등)와 상호작용할 수 있다 true

기본값이 보수적이라는 점에 주목하세요. 아무 주석이 없는 도구는 환경을 바꾸고, 파괴적일 수 있으며, 외부 세계와 상호작용할 수 있다고 가정됩니다. 그리고 사양은 신뢰할 수 있는 서버에서 온 것이 아니면 클라이언트가 주석을 신뢰할 수 없는 것으로 간주해야 한다고(MUST) 경고합니다. 주석은 서버의 자기소개이지 보증서가 아닙니다. 자동 승인 같은 권한 결정은 서버의 신뢰 등급과 호스트 정책에 묶어야 합니다.

인증과 최소 권한

사양의 인증 범위는 전송에 따라 다릅니다. HTTP 기반 전송은 MCP 인가(Authorization) 사양을 따르도록 권하고(SHOULD), stdio는 이 사양을 따르지 말고 실행 환경에서 자격 증명을 받으라고 권합니다.

보안 모범 사례 문서의 핵심 규칙은 다음과 같습니다.

위험 규칙
토큰 전달(token passthrough) MCP 서버는 자신에게 발급되지 않은 토큰을 받아서는 안 된다(MUST NOT). 받은 토큰을 하위 API에 그대로 넘기면 감사 추적과 속도 제한 같은 통제가 무너진다
혼동된 대리인(confused deputy) 고정 클라이언트 ID로 제3자 API에 붙는 프록시 서버는 클라이언트별 동의를 받은 뒤에 제3자 인가로 넘어가야 한다
핸들 탈취 핸들 보유를 인증으로 취급하지 않는다. 핸들을 서버 쪽에서 인증된 사용자에 묶는다
과도한 scope 최소 scope로 시작하고, 권한이 필요한 작업을 처음 시도할 때 WWW-Authenticate의 scope 요구로 단계적으로 올린다. *, all 같은 포괄 scope는 피한다
로컬 서버 원클릭 설치를 지원하는 클라이언트는 실행될 명령 전체를 잘림 없이 보여 주고 명시적 승인을 받아야 한다. 최소 권한 샌드박스에서 실행하길 권한다

scope는 토큰이 허락하는 작업의 범위입니다. 마스터키 하나보다 지금 필요한 방 열쇠만 주고 필요할 때 더 내주는 편이 잃어버렸을 때 피해가 작습니다. tools/list는 연결에 따라 달라져서는 안 되지만 요청의 인가에 따라서는 달라질 수 있어, 서버는 호출자의 scope가 허용하는 도구만 보여 줄 수 있습니다. 다만 숨기는 것은 편의일 뿐, 실제 차단은 매 호출의 서버 측 권한 검사가 맡습니다.

사람 승인과 보안 요구 사항

사양의 도구 보안 요구는 서버와 클라이언트의 몫이 다릅니다.

주체 요구
서버 (MUST) 모든 입력 검증, 적절한 접근 제어, 호출 빈도 제한, 출력 정제
클라이언트 (SHOULD) 민감한 작업 전 사용자 확인, 호출 전에 보낼 입력을 사용자에게 보여 주기(의도치 않은 유출 방지), 결과를 모델에 넘기기 전 검증, 타임아웃, 감사 로그

어떤 호출에 사람 확인을 걸지는 5장에서 정합니다. 여기서는 확인 화면에 실제로 보낼 입력을 보여 줘야 한다는 점만 기억하세요. "메일을 보낼까요?"만 묻는 확인은 수신자가 바뀐 호출도 통과시킵니다.

깊이 읽기

도구가 수백 개로 늘 때

MCP 서버를 여럿 붙이면 도구 정의만으로 컨텍스트의 상당 부분이 찹니다. MCP의 클라이언트 모범 사례 문서는 이때 점진적 발견(progressive discovery)을 권합니다. 호스트가 도구 정의를 받아 두되 컨텍스트에는 바로 넣지 않고, 가벼운 search_tools 메타 도구로 필요한 도구만 찾아 전체 정의를 불러오게 하는 방식입니다. 문서는 목록(이름과 한 줄 설명), 상세(한 도구의 전체 스키마), 실행의 세 층을 예로 들고, 도구 정의가 컨텍스트 창의 일정 비율(예: 1~5%)을 넘으면 전환하라고 권합니다. 검색은 키워드, 임베딩, 작은 모델의 선택, 또는 그 조합으로 하며, 일부 모델 제공자는 내장 도구 검색을 제공합니다.

함정은 프롬프트 캐시입니다. 대부분의 제공자는 tools 배열을 포함한 프롬프트 앞부분을 캐시하므로, 대화 중에 도구 정의를 넣고 빼거나 순서를 바꾸면 캐시가 깨집니다. 문서는 새로 찾은 정의를 캐시 지점 뒤에 덧붙이거나, 배열이 바뀌지 않도록 도구 이름과 인자를 받아 대신 호출하는 고정된 call_tool 메타 도구 하나로 모든 호출을 보내라고 권합니다. 사양이 tools/list를 결정적인 순서로 돌려주라고 권하는 이유도 같습니다.

모델이 코드로 도구를 부를 때

도구를 직접 부르면 호출마다 결과 전체가 모델을 거칩니다. 로그 수천 줄을 읽어 고유 오류마다 티켓을 만드는 일이라면, 모델은 그 수천 줄을 모두 읽어야 합니다. 프로그래밍 방식 도구 호출(code mode)은 모델이 도구를 부르는 코드를 쓰고, 그 코드가 샌드박스에서 실행되어 최종 결과만 모델에 돌아오게 합니다. 중간 데이터가 컨텍스트에 들어오지 않으니 토큰과 지연이 크게 줍니다.

대신 코드 실행이라는 새 공격 면이 생깁니다. MCP 문서가 드는 조건은 여섯입니다. 샌드박스에는 직접 네트워크 접근을 주지 않고 모든 통신을 호스트 중개자로 보낸다, 자격 증명은 호스트가 쥐고 생성된 코드에 노출하지 않는다, 스크립트를 승인했다고 그 안의 모든 호출을 승인한 것으로 보지 않는다, 한 서버의 결과는 다른 서버에 대해 신뢰할 수 없는 입력으로 다룬다, 실행 시간과 메모리를 제한한다, 샌드박스 출력은 검증하고 잘라 낸 뒤 모델에 넘긴다. 호출이 한두 번뿐인 대화형 작업에서는 복잡도가 이득보다 클 수 있으니, 대량 데이터를 거르고 모으는 작업에 먼저 적용하세요.

도구 변경을 계약으로 관리한다

스키마가 그대로여도 설명 한 줄, 결과 필드의 뜻 하나가 바뀌면 에이전트의 행동이 달라집니다. 그래서 이름·설명·스키마·업무 의미를 함께 버전으로 묶고, 변경 이력을 남기고, 배포 전 평가로 확인합니다. Anthropic은 실제 과제로 실행 시간·도구 호출 수·토큰·도구 오류를 재되, 평가 세트에만 맞춰지지 않도록 따로 떼어 둔 검증 세트(held-out test set)로 확인하라고 권합니다. 리서치 시스템 글은 도구 설명을 고쳐 쓰는 에이전트 덕분에 이후 작업 완료 시간이 40% 줄었다고 보고합니다. 서버가 목록을 바꾸면 notifications/tools/list_changed로 알릴 수 있으니, 클라이언트는 이때 캐시한 목록을 버리고 다시 받습니다.

실무 장면

너울애드: 예산 변경 도구에 안전장치 넣기

광고 대행사 '너울애드'의 운영 에이전트는 광고 플랫폼 API 11개를 그대로 감싼 도구를 썼다. 지난 분기 사고는 두 종류였다. 이름이 비슷한 캠페인('가을특가_검색'과 '가을특가_검색_재방문')을 바꿔 집행한 일이 9건, 타임아웃 뒤 재시도로 "일 예산 20% 증액"이 두 번 적용된 일이 3건이었다.

팀은 세 대안을 검토했다. 시스템 프롬프트에 주의 문구를 늘리는 안은 지난 분기에도 했던 일이라 기각했다. 도구 11개를 유지하고 설명만 다듬는 안은 상대값 증액과 중복 실행이라는 구조적 원인을 못 없앤다고 보고 기각했다. 택한 안은 도구 재설계였다.

조회는 campaigns_find(query, status) 하나로 합치고 결과에 캠페인 이름·ID·현재 일 예산·담당자를 담았다. 변경은 둘로 나눴다. budget_change_preview(campaign_id, new_daily_budget_krw)는 절댓값만 받고, 바뀌기 전후 금액과 월 예상 집행액, 10분 유효한 미리보기 ID를 돌려준다. budget_change_apply(preview_id)는 미리보기 ID만 받으며, 같은 ID로 두 번 와도 한 번만 적용한다. 기존 대비 50%를 넘는 증액은 isError: true와 함께 "담당자 승인이 필요합니다. 승인 요청 도구를 쓰세요"를 돌려준다. 타임아웃이 나면 에이전트는 재시도 대신 미리보기 ID로 적용 여부를 조회하게 했다.

과거 사고 12건을 변형해 만든 과제 40건과, 따로 떼어 둔 새 과제 25건으로 평가했다. 두 세트 모두 엉뚱한 캠페인 변경과 중복 증액이 0건이었고, 과제당 도구 호출은 4.8회에서 2.6회로 줄었다.

파랑소프트: 배포 MCP 서버의 권한 다시 긋기

개발 플랫폼 기업 '파랑소프트'는 에이전트가 배포 상태를 조회하고 문제가 생기면 롤백하도록 원격 MCP 서버를 만들었다. 첫 버전은 빨리 만드는 데 집중했다. 사용자가 가진 CI 시스템 토큰을 그대로 받아 CI API에 넘겼고, scope는 deploy:* 하나였으며, 배포 ID만 알면 누구의 배포든 롤백할 수 있었다.

보안 검토는 세 가지를 지적했다. 서버가 자신에게 발급되지 않은 토큰을 받아 넘기고 있다(토큰 전달). 조회만 하는 사용자도 롤백 권한을 가진다(과도한 scope). 배포 ID를 권한처럼 쓰고 있다(핸들을 인증으로 취급).

다시 설계한 서버는 MCP 인가로 발급된 자신의 토큰만 받고 대상(audience)을 검증한다. CI API에는 서버 자신의 자격 증명으로 접근하되, 매 호출마다 사용자의 배포 권한을 사내 권한 시스템에 묻고 하위 호출의 감사 기록에 실제 사용자 식별자를 남긴다. 처음에는 deploy:read만 요청하고, 롤백을 처음 시도하면 deploy:rollback을 요구해 단계 상승을 유도한다. tools/list는 scope에 따라 롤백 도구를 숨기지만, 롤백 호출은 늘 서버에서 다시 검사한다. 롤백 도구에는 destructiveHint: true를 붙였고, 호스트는 사내 서버를 신뢰 서버로 등록해 이 주석을 근거로 확인 화면을 띄운다.

기각한 대안은 "도구 설명에 '롤백은 신중히'라고 적기"였다. 설명은 모델에게 주는 권고일 뿐, 권한 경계가 아니기 때문이다.

자주 틀리는 지점

  1. "API를 빠짐없이 도구로 노출하면 에이전트가 알아서 고른다." 기존 API를 감싸면 빨리 만들 수 있어 끌리지만, 조합 부담이 모델에게 가고 비슷한 도구끼리 헷갈립니다. 작업 흐름 단위의 소수 도구가 낫습니다.
  2. "오류는 프로토콜 오류로 던지는 것이 정석이다." 일반 API 관행과 같아 자연스러워 보이지만, 모델이 고칠 수 있는 업무 오류는 isError와 구체적 설명으로 돌려줘야 스스로 회복합니다.
  3. "readOnlyHint가 붙었으니 안전하다." 사양에 정의된 필드라 공식 보증처럼 보이지만, 주석은 서버의 자기 주장입니다. 신뢰할 수 없는 서버의 주석으로 권한 결정을 하면 안 됩니다.
  4. "받은 토큰을 하위 API에 그대로 넘기면 편하다." 구현이 가장 간단해 끌리지만, 사양이 금지하는 토큰 전달입니다. 감사 추적, 속도 제한, 신뢰 경계가 함께 무너집니다.
  5. "결과를 잘라 주면 크기 문제는 끝난다." 토큰 수는 줄지만, 잘렸다는 사실과 범위를 알리지 않으면 에이전트는 일부를 전부로 믿고 결론을 냅니다.
  6. "MCP가 무상태가 되었으니 서버에 상태를 두면 안 된다." 이름이 그렇게 들리지만, 상태는 둘 수 있습니다. 연결이 아니라 명시적 핸들에 묶고 핸들마다 권한을 검사하면 됩니다.
  7. "목록에서 숨긴 도구는 호출될 수 없다." 화면에서 사라지니 막힌 것처럼 보이지만, 목록 필터는 편의입니다. 실제 차단은 매 호출의 서버 측 권한 검사가 합니다.

한눈에 정리

  • 도구는 ACI다. 틀릴 수 있는 입력을 형식에서 없앤다(절대 경로, 절댓값, 단위가 든 이름).
  • 엔드포인트 단위가 아니라 작업 흐름 단위로 소수의 도구를 만들고, 네임스페이스로 구분한다.
  • 결과는 다음 행동에 쓸 필드로, 상세도는 response_format으로, 크기는 페이지·필터·잘라내기 안내·참조로 관리한다.
  • 업무 오류는 isError와 회복 지시로. 실행 여부를 모르는 오류는 재시도보다 조회.
  • 되돌리기 어려운 변경은 미리보기와 확정으로 나누고 멱등하게 만든다.
  • 2026-07-28 MCP는 무상태다. _meta의 버전·능력 필수, 상태는 핸들로, 핸들은 권한이 아니다.
  • 최대 타임아웃은 진행 알림과 상관없이 강제하고, 긴 작업은 Tasks 확장을 검토한다.
  • 주석의 기본값은 보수적이며, 신뢰할 수 없는 서버의 주석은 믿지 않는다.
  • 토큰 전달 금지, 최소 scope에서 단계 상승, 매 호출 서버 측 권한 검사.

공식 문서로 확인하기

셀프 체크

2-1. 사람 개발자에게 편한 API와 에이전트에게 좋은 도구가 달라지는 이유를 컨텍스트 비용과 선택 오류의 관점에서 설명하라.

2-2. 응답 상세도를 고르는 매개변수(response_format 등)는 어떤 문제를 해결하며, 에이전트는 언제 상세 응답을 골라야 하는가?

2-3. 도구가 결과를 잘라 돌려줄 때 결과 안에 함께 적어야 할 정보 두 가지와, 그것이 없을 때 에이전트가 저지르는 실수를 설명하라.

2-4. 2026-07-28 사양에서 서버가 같은 연결의 이전 요청으로부터 끌어와서는 안 되는 정보 세 가지를 들고, 그 정보가 대신 실리는 곳을 말하라.

2-5. 주석이 하나도 없는 도구를 클라이언트는 어떻게 가정하게 되는가? 사양의 주석 기본값과 연결해 설명하라.

2-6. 토큰 전달이 감사 추적과 속도 제한을 무너뜨리는 이유를 각각 한 문장으로 설명하라.

2-7. 사내 도서 대여 시스템의 '대여 연장' 기능을 에이전트용 도구로 만든다. 오용하기 어렵도록 도구 이름, 매개변수(형식 포함), 업무 오류 하나와 그 메시지, 사람 확인이 필요한 조건을 각각 정하라.

실습 과제

실습

도구 하나 다시 설계하고 측정하기 (60분)

  1. 에이전트가 자주 쓰거나 자주 틀리는 도구 하나를 고른다.
  2. 실제 사용 기록에서 과제 20건을 뽑아 15건은 개선용, 5건은 검증용으로 나눈다.
  3. 현재 도구로 돌려 과제당 도구 호출 수·토큰·도구 오류를 기록한다.
  4. 이 장의 원칙으로 도구를 다시 설계한다(작업 흐름 단위, 단위가 든 매개변수 이름, 기본 개수와 잘라내기 안내, 회복 지시가 있는 오류).
  5. 개선용과 검증용 과제를 모두 다시 돌려 세 지표를 비교한다.

완료 기준: 검증용 과제에서 세 지표 중 둘 이상이 개선되고 과제 성공률이 떨어지지 않았으면 완료다. 개선되지 않았다면 트랜스크립트에서 모델이 도구를 오해한 지점을 찾아 설명을 고친 기록이 있어야 한다.

실습

권한 표와 서버 측 검사 만들기 (45분)

  1. 운영 중인 MCP 서버(또는 도구 묶음) 하나의 도구를 모두 나열한다.
  2. 도구마다 읽기·쓰기 여부, 되돌림 가능성, 필요한 scope, 붙일 주석을 표로 적는다.
  3. 최소 scope 집합(처음 연결 시)과 단계 상승이 필요한 scope를 나눈다.
  4. 쓰기 도구 하나를 골라, 호출자의 권한을 대상 자원과 대조하는 서버 측 검사 코드를 쓰고, 권한 없는 호출에 대한 오류 메시지를 정한다.

완료 기준: 권한 없는 사용자의 호출이 서버 검사에서 거절되는 시험이 통과하고, 표에 모든 도구의 scope와 주석이 빠짐없이 적혀 있으면 완료다.