레슨 3: 흔한 도구 다섯 유형: 읽기, 쓰기, 실행, 검색, 호출
학습 목표:
- 흔한 도구를 피해 규모에 따라 다섯 유형으로 나누고, 각각의 전형적인 시그니처 짚어 내기
- 명령 실행 도구가 왜 나머지 넷과 다른 위험 등급에 놓이는지 설명하기
- 검색 도구가 왜 파일 전체가 아니라 일치하는 스니펫을 반환하는지 설명하기
전제: 레슨 2를 마쳤고, 도구 호출의 왕복 구조를 이해하고 있다 | 이전: 레슨 2 << | 다음: 레슨 4 >>
표에서 시작한다
이 표를 정렬하는 기준은 무엇일까요? 알파벳 순서가 아닙니다. '호출 한 번이 얼마나 넓은 범위의 피해를 일으킬 수 있는가', 즉 그 호출의 **영향 반경(blast radius)**입니다. 읽기 전용 도구의 영향 반경은 사실상 0입니다. 엉뚱한 파일을 읽어도 이번 대화 한 턴이 어긋날 뿐입니다. 파일 쓰기는 기존 내용을 덮어쓸 수 있습니다. 명령 실행은 시스템 전체에 무슨 짓이든 할 수 있습니다. 유형을 하나씩 짚어 가다 보면, '무엇을 할 수 있는가'를 넘어 각 유형만 걸려 넘어지는 함정이 하나씩 있다는 것을 보게 됩니다.
읽기: 가장 안전하지만 위험이 0은 아니다
파일 읽기 도구의 시그니처는 대개 이렇게 생겼습니다.
반환값은 파일 내용 그 자체이고, 나중에 모델이 참조할 수 있도록 대개 줄 번호가 붙습니다.
파일을 읽는 것은 어떤 상태도 바꾸지 않습니다. 모델이 엉뚱한 것을 읽거나 너무 많이 읽어도, 최악의 결과는 이번 한 턴에 관련 없는 내용이 섞이는 정도입니다. 게다가 모델은 잘못 읽었다는 것을 알아채고 다시 읽는 편입니다. '가장 안전한' 유형이라 부르는 이유가 그것입니다. 위험이 없다는 뜻이 아니라, 위험이 이 대화의 경계를 벗어나지 못한다는 뜻입니다.
진짜 위험은 애초에 건드려서는 안 되는 파일을 읽는 것입니다. 에이전트에게 ~/.ssh/id_rsa나 프로젝트의 .env를 읽을 권한이 있다면, "이 디렉터리에 뭐가 있는지 보여 줘" 같은 순진해 보이는 요청 하나가 비밀 키를 글자 그대로 대화 컨텍스트로 끌어올릴 수 있습니다. 그 시점부터, 그 컨텍스트가 모델에 의해 출력되거나, 로그에 기록되거나, 이후의 '외부 API 호출' 도구에 실려 나가는 순간 유출은 이미 벌어진 것입니다. 파일 읽기 도구가 '읽기 전용이니 그냥 열쇠를 다 넘겨도 된다'가 아니라 거의 언제나 경로 허용 목록이나 샌드박스와 함께 다니는 이유입니다. 그런 경계를 어떻게 세우는지는 레슨 5에서 자세히 다룹니다.
쓰기: 결과가 더 이상 대칭이 아닌 지점
파일 쓰기 도구는 읽기보다 파라미터가 하나 많고, 안전장치는 하나 적습니다.
반환값은 대개 단순한 상태값뿐입니다.
문제는 반환값이 아니라 호출 그 자체입니다. 읽기가 잘못되면 다시 읽으면 되고 달라진 것은 없습니다. 쓰기가 잘못되면 — 예를 들어 모델이 path를 잘못 채웠거나 content에 있어야 할 내용의 절반이 빠졌다면 — 원본 파일의 내용은 이미 덮어써졌고, 버전 관리나 백업이 없는 한 되돌릴 수 없습니다. 이것이 '읽기 도구와 쓰기 도구의 비대칭'입니다. 두 호출의 생김새는 거의 같지만(path에 파라미터 몇 개), 한쪽은 마음껏 재시도할 수 있고 다른 쪽은 호출할 때마다 도박을 겁니다.
그래서 책임 있는 쓰기 도구는 보호 장치를 한 겹 덧댑니다. 예를 들어 편집하기 전에 그 파일을 먼저 읽었을 것을 요구하거나(모델이 기억에 의존해 편집하는 것을 막기 위해), 맨숭맨숭한 '성공' 대신 이전 내용과 새 내용의 diff를 반환해 호출자(호스트 애플리케이션)가 실제로 디스크에 반영되기 전에 변경을 보여 줄 기회를 갖게 합니다. 여기서 다룰 초점은 아닙니다. 레슨 4에서 인터페이스 설계를 다루며 펼쳐 보겠습니다.
실행: 그 자체로 하나의 위험 등급
명령 실행 도구는 다섯 중 시그니처가 가장 밋밋해 보입니다.
문자열 하나가 들어가고, stdout과 stderr, 종료 코드가 나옵니다.
함정은 이 command 필드가 본질적으로 열려 있는 입구라는 데 있습니다. '이 파일을 지운다'거나 '이 줄을 읽는다' 같은, 스키마로 경계가 그어진 특정 동작이 아니라 임의의 셸 스크립트입니다. rm -rf, 데이터를 외부 서버로 실어 보내는 curl, 오염된 패키지를 끌어오는 npm install — 전부 그 문자열 하나 안에 들어갑니다. 나머지 네 유형(읽기, 쓰기, 검색, API 호출)은 시그니처를 어떻게 설계하든 파라미터 구조가 할 수 있는 일을 제한합니다. 명령 실행 도구의 능력 경계는 운영체제 전체의 능력 경계입니다. 그래서 별도 등급인 것입니다. '조금 더 위험하다'가 아니라, 위험의 자릿수가 다릅니다.
바로 그 때문에 공식 문서는 이 유형만을 위해 운영체제 수준의 격리를 설계합니다. 파일 시스템 접근과 네트워크 접근은 별개의 두 샌드박스 계층이고, 모델이 프롬프트 인젝션에 조종당해 위험한 명령을 기어이 실행하려 들더라도 OS 경계는 그와 무관하게 버팁니다. 모델이 협조할 '의사'가 있는지에 기대지 않습니다1. 밝혀 둔 동기는 직설적입니다. 프롬프트 인젝션이 성공하더라도 그 영향이 온전히 갇혀 샌드박스를 벗어나지 못하게 하는 것이 목표입니다2. 그 격리를 어떻게 설정하는지는 레슨 5에서 다룹니다. 지금은 하나만 붙잡아 두세요. '명령 실행' 시그니처가 나타나는 곳이라면 어디든, 다섯 유형 중 추가 제약이 가장 필요한 유형으로 기본 취급하세요.
검색: 세상 전부가 아니라 위치를 돌려준다
검색 도구(예를 들어 코드베이스 전체에서 키워드나 정규식을 찾는 도구)는 시그니처에 '얼마나 돌려줄지 제한하는' 파라미터를 함께 두는 경우가 많습니다.
반환값은 파일 자체가 아니라 '일치가 어디에 있고 그 주변이 어떻게 생겼는지'입니다.
이 도구가 일치한 파일의 전체 내용을 그대로 밀어 넣는다면 문제가 둘 생깁니다. 첫째는 토큰 문제입니다. 검색 한 번에 50개 파일이 걸리고 각각 수백 줄이라면, 그것이 전부 컨텍스트에 쏟아지고, 이 도구 호출 하나가 이번 턴의 입력 예산을 통째로 먹어 치워 모델이 이어서 일할 여지가 남지 않습니다3. 도구 description을 잘 쓰고 입력과 출력의 경계를 통제하는 것 자체가 도구를 쓸 만하게 만드는 기본 요건이기도 합니다4. 둘째 문제가 더 중요합니다. 검색의 요점은 '관련 있을 만한 것을 전부 읽어 두기'가 아니라 '모델이 다음에 어디를 봐야 할지 알아내도록 돕기'입니다. 일치 위치와 짧은 문맥 스니펫을 돌려주면, 모델이 그 스니펫을 읽고 스스로 판단합니다. "이 결과 중 두 번째가 내가 찾던 것 같으니, 그 파일의 전체 내용을 따로 읽어 보자." 검색 도구와 읽기 도구가 함께 일하는 방식이 이것입니다. 검색이 범위를 좁히고, 읽기가 세부를 가져옵니다. '파일 전체'가 아니라 '일치 위치'를 반환하는 것이야말로, 쓸모 있을지 모를 것을 한 번에 쏟아붓는 대신 모델이 필요로 하는 다음 단서를 주는 일입니다.
외부 API 호출: 실패는 예외가 아니라 일상이다
앞의 네 유형은 대체로 로컬 시스템 안에 머뭅니다. 외부 API 호출은 다릅니다. 네트워크를 건너, 여러분이 통제하지 못하는 서비스로 갑니다.
정상 반환은 이렇게 생겼습니다.
하지만 외부 서비스는 요청량을 제한하고, 타임아웃을 내고, 권한이 없다며 요청을 거절하고, 여러분이 호출하는 사이에 인터페이스를 바꾸기도 합니다. 이런 것들은 '예상치 못한 상황'이 아니라 이 유형의 정상 운영 조건입니다. 이 도구가 쓸 만한지를 실제로 가르는 것은 '잘 돌아갈 때 무엇을 반환하는가'가 아니라 '실패할 때 무엇을 반환하는가'입니다.
이 에러 메시지는 여러분을 위한 것이 아니라 모델을 위한 것입니다. 다음에 재시도할지 전략을 바꿀지는 모델이 그 error 필드를 읽을 수 있느냐에 달려 있습니다. MCP 명세는 이것을 프로토콜에 그대로 적어 두었습니다. 클라이언트는 도구 실행 에러를 언어 모델에 제공해, 모델이 스스로 바로잡고 다시 시도할 기회를 갖게 해야 한다는 것입니다5. 다시 말해, 429를 조용히 삼키고 '호출 실패'만 돌려주는 도구는 모델에게서 스스로 고칠 기회를 빼앗는 것이고, retry_after 같은 구체적인 정보를 실어 돌려주는 도구가 '실패'를 정상적인 작업 흐름의 일부로 설계한 것입니다.
외부 API 호출 유형은 위험을 한 겹 더 끌고 옵니다. 이 에이전트가 비공개 데이터를 읽을 수 있으면서 동시에 신뢰할 수 없는 콘텐츠(예: 사용자가 붙여 넣은 웹 텍스트 덩어리)에 노출되어 있고, 게다가 바깥으로 메시지나 요청을 보낼 수도 있다면, 이 셋이 합쳐진 것이 보안 연구에서 "lethal trifecta"(치명적 3요소)라고 부르는 것입니다. 공격자는 여러분의 시스템에 침입할 필요가 없습니다. 에이전트가 읽을 콘텐츠에 지시를 숨겨 두고, 에이전트가 스스로 비공개 데이터를 실어 내게 하면 됩니다6. 이 주제는 레슨 5에서 따로 펼칩니다. 지금은 이것만 알아 두세요. 외부 API 호출은 그 사슬의 마지막이자 가장 결정적인 고리입니다. 데이터가 실제로 여러분의 시스템을 빠져나가는 출구이기 때문입니다.
정리
- 다섯 유형의 위험은 고르게 퍼져 있지 않다. 파일 읽기의 결과가 가장 가볍고, 파일 쓰기부터 되돌릴 수 없어지기 시작하며, 명령 실행의 영향 반경은 운영체제 전체와 같고, 검색과 외부 API 호출은 각자 별개의 함정을 지닌다
- 읽기 도구와 쓰기 도구의 핵심 차이는 안전하게 재시도할 수 있는가다. 잘못 읽으면 다시 읽으면 되지만, 잘못 쓰면 원본 내용이 영영 사라질 수 있다
- 명령 실행 도구가 OS 수준 샌드박스라는 방벽을 필요로 하는 것은, 그
command 파라미터가 나머지 넷처럼 스키마 구조로 묶이지 않은 열린 문자열이기 때문이다1 2
- 검색 도구가 파일 전체가 아니라 일치 위치와 스니펫을 반환하는 것은, 첫째로 토큰을 아끼기 위해서이고, 둘째로 '위치 찾기'와 '세부 읽기'를 갈라 모델이 이어 갈 단서를 건네기 위해서다3
- 외부 API 호출 도구는 실패 정보(에러 유형, 재시도 가능 여부)를 삼키지 말고 모델에게 그대로 실어 보내야 한다. 이 유형에서 실패는 예외가 아니라 일상이다5
다음 레슨에서는 이 다섯 유형의 '시그니처'를 뜯어봅니다. 모델이 한 번에 제대로 호출하도록 좋은 도구 이름, description, 파라미터 스키마, 반환값을 어떻게 쓸 것인가입니다.
>> 레슨 4: 도구 인터페이스 설계: 이름, 설명, 파라미터, 반환값