글 / LangGraph·MCP

에이전트 도구의 읽기·쓰기 분리

조회 도구만 즉시 실행을 허용하고, 변경 동작은 제안으로 접수하여 사람이 승인한 뒤 코드가 실행한다.

작성일 · 읽는 시간 2분 · MCP 2026-07-28 기준

조회와 변경의 혼재

조회와 변경이 하나의 도구에 혼재하면 승인 규칙을 적용할 수 없다. 모든 호출에 승인을 요구하면 조회에도 불필요한 승인 절차가 추가되고, 모든 호출을 자동으로 실행하면 모델의 잘못된 판단이 즉시 설비 값을 변경한다.

분리 기준

구분 예 실행
조회 도구 현재 값 읽기 모델이 즉시 호출
제안 도구 설정값 변경 제안 제안만 기록
실행 코드 설정값 쓰기 사람이 승인한 뒤 코드가 호출

값을 실제로 변경하는 함수는 도구로 노출하지 않는다. 모델이 호출할 수 있는 범위는 제안까지이다.

조회·제안 도구와 승인 후 실행 코드의 분리 구조

pythontools.py
import uuid

SETPOINT = {"oven-1": 180.0}
PROPOSALS: dict[str, dict] = {}


def get_setpoint(device: str) -> float:
    """조회 도구. 값을 바꾸지 않는다."""
    return SETPOINT[device]


def propose_setpoint(device: str, value: float) -> str:
    """제안 도구. 기록만 하고 제안 번호를 돌려준다."""
    proposal_id = uuid.uuid4().hex[:8]
    PROPOSALS[proposal_id] = {"device": device, "value": value}
    return proposal_id


def apply_proposal(proposal_id: str, approver: str) -> None:
    # 도구가 아니다. 승인 화면의 코드에서만 부른다
    p = PROPOSALS.pop(proposal_id)
    SETPOINT[p["device"]] = p["value"]
    print(approver, "승인:", p)

제안 기록부터 사람 승인 뒤 설정값 쓰기까지의 순서

Annotation의 효력

MCP 도구에는 도구의 성격을 나타내는 annotation을 지정할 수 있다.

  • readOnlyHint: 환경을 변경하지 않음을 나타낸다. 기본값은 false이다.
  • destructiveHint: 삭제하거나 덮어쓸 수 있음을 나타낸다. 기본값은 true이다.
  • idempotentHint: 같은 인자로 다시 호출해도 추가 변경이 없음을 나타낸다. 기본값은 false이다.

사양에는 이 값들이 힌트일 뿐이라고 명시되어 있다. 서버가 자체적으로 선언하는 값이므로 실제 동작과 일치한다는 보장이 없고, 클라이언트는 신뢰하는 서버가 아니면 이 값을 신뢰하지 않아야 한다. annotation은 승인 화면에 표시하는 정보로만 사용하고, 차단은 위 구조와 서버 측 권한 검사에 위임한다.