본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 10. 12:37

AI 에이전트에게 Shopify 스토어 쓰기 권한을 부여했습니다. 재앙을 막기 위해 준비한 모든 것들

요약

AI 에이전트에게 Shopify 스토어 쓰기 권한을 부여할 때 발생할 수 있는 위험을 방지하기 위한 가드레일 구축 전략을 다룹니다. MCP 서버를 통해 에이전트가 직접 작업을 수행할 때 필요한 보안 설계 원칙을 제시합니다.

핵심 포인트

  • 기본 권한은 읽기 전용으로 설정하고 쓰기 권한은 토큰별로 선택(opt-in)해야 함
  • 제한 사항은 프롬프트가 아닌 도구 스키마(tool schema) 단계에서 강제해야 함
  • 수치 데이터에 대해 서버 측 상한선(server-side ceiling)을 반드시 설정해야 함
  • 외부 발송 메시지는 에이전트가 초안을 작성하고 사람이 최종 승인하는 구조 권장

지난주 저는 2년 전이었다면 무모하게 들렸을 무언가를 출시했습니다. 바로 AI 에이전트가 상인의 실제 Shopify 스토어에 **쓰기 (write)**를 할 수 있게 해주는 MCP (Model Context Protocol) 서버입니다. 할인 코드를 생성하고, 고객 세그먼트 (customer segments)를 구축하며, 실제 주문 데이터를 바탕으로 WhatsApp 캠페인 초안을 작성합니다.

읽기 전용 (Read-only) 에이전트 통합은 이제 어디에나 있으며, 그 자체로 괜찮습니다. 하지만 읽기 전용 에이전트는 그저 대시보드를 입고 있는 챗봇일 뿐입니다. 유용한 버전은 '직접 실행하는' 버전입니다. 위험한 버전 또한 '직접 실행하는' 버전입니다. 환각 (hallucination) 현상으로 인한 SELECT 문은 잘못된 답변에 그치지만, 환각으로 생성된 할인 코드는 공짜 제품이 문밖으로 나가는 것을 의미합니다.

따라서 쓰기 권한을 활성화하기 전에, 저는 에이전트가 스토어에 해를 끼칠 수 있는 모든 방법을 나열해 보았습니다. 그런 다음 각 실패 모드 (failure mode)당 하나의 가드레일 (guardrail)을 구축했습니다. 그 목록은 다음과 같습니다. 목록은 짧지만, 비즈니스 데이터에 접촉하는 모든 에이전트 인터페이스에 일반화될 수 있다고 생각합니다.

1. 기본값은 읽기 전용. 쓰기는 토큰별 선택 사항 (opt-in).

게으른 설계 방식은 앱이 할 수 있는 모든 것을 수행할 수 있는 하나의 API 키를 사용하는 것입니다. 대신, 모든 에이전트 토큰은 읽기 전용으로 시작합니다. 고객 목록을 나열하고, 세그먼트를 조사하며, 캠페인 통계를 읽는 식입니다. 쓰기 기능은 상인이 토큰별로 전환하는 별도의 플래그 (flag)입니다:

{
  "token": "fav_mcp_…",
  "scopes": ["read"],          // 기본값
...

이것은 당연하게 들립니다. 실제로 당연합니다. 하지만 에이전트 통합 과정에서 개발 중 발생하는 마찰(friction) 때문에 제가 보는 가장 많이 생략되는 단계이기도 합니다. 그럼에도 불구하고 마찰을 만드십시오. "에이전트가 모든 것을 읽을 수는 있었지만, 그 동작을 수행할 수는 없었다"라는 문장은 나중에 당신에게 정말로 필요한 문장이 될 것입니다.

2. 제한 사항(Caps)은 프롬프트가 아니라 도구 스키마 (tool schema)에 존재해야 합니다.

초기에는 "30% 이상의 할인을 절대 생성하지 마세요"와 같은 시스템 프롬프트 (system prompt)를 사용했습니다. 그것이 얼마나 지속 가능할지는 짐작하실 수 있을 것입니다. 프롬프트는 제안일 뿐이지만, 스키마 (schema)는 물리 법칙입니다.

따라서 제한 사항을 도구의 입력 유효성 검사 (input validation) 자체, 즉 그 형태 안으로 옮겼습니다:

// create_discount — 프롬프트 측이 아닌 서버 측에서 유효성 검사됨
{
  percentage: z.number().min(1).max(100),   // 서비스 내에서 강제되는 하드 천장 (hard ceiling)
...

모델이 250% 할인이나 음수 할인을 요청할 경우, 스키마 (schema) 단계에서 호출이 실패합니다. 판단을 내리거나 "모델이 보통은 잘 작동하니까"라고 낙관할 필요가 없습니다. 에이전트는 도구의 문법 (grammar) 상에서 위험한 요청을 문자 그대로 표현할 수조차 없습니다.

일반적인 규칙은 다음과 같습니다: 에이전트가 제어하는 모든 수치 데이터에는 서버 측 상한선 (server-side ceiling)이 필요합니다. 할인율, 메시지 전송량, 환불 금액, 배치 크기 (batch size) 등이 이에 해당합니다. 만약 상한선이 자연어 (natural language)로만 존재한다면, 그것은 존재하지 않는 것과 같습니다.

3. 외부 발송 메시지: 에이전트가 초안을 작성하고, 사람이 전송합니다.

쓰기 작업 (writes)이라고 해서 모두 위험도가 같은 것은 아닙니다. 저는 결국 모든 도구를 하나의 질문으로 분류했습니다 — "이 작업이 잘못되면 어떻게 되는가?"

  • 되돌릴 수 있는 쓰기 (Reversible writes) (세그먼트 생성, 고객 태그 지정): 에이전트가 수행하도록 둡니다. 최악의 경우 세그먼트를 삭제하는 정도입니다.
  • 비용이 들지만 수정 가능한 쓰기 (Costly-but-fixable writes) (한도가 정해진 할인 코드): 위에서 언급한 스키마 상한선 (schema ceilings)을 적용하여 허용합니다.
  • 되돌릴 수 없는, 고객 대상 쓰기 (Irreversible, customer-facing writes) (2,000명에게 WhatsApp 캠페인 방송): 에이전트에게는 draft_campaign 도구만 부여합니다. send_campaign 도구는 존재하지 않습니다.

마지막 사례는 기능이 누락된 것이 아니라, 의도된 설계입니다. 방송은 전송 취소를 할 수 없습니다. 잘못된 세그먼트와 의욕 과잉인 에이전트가 만나면, 12명에게 보내려던 메시지를 2,000명의 고객이 받게 됩니다. 이는 롤백 (rollback)으로도 해결할 수 없는 브랜드의 문제입니다. 따라서 프로세스는 상인의 대시보드에 초안이 머물러 있고, 오직 사람만이 누를 수 있는 전송 버튼이 있는 상태에서 의도적으로 종료됩니다.

(에이전트가 예약을 잡는 동안 사람이 "결제"를 누르는 것에 대해 쓴 제 지난 포스트를 읽어보셨다면 — 원리는 같고 영향 범위 (blast radius)만 다릅니다. 패턴은 일관됩니다: 에이전트가 90%를 수행하고, 사람이 되돌릴 수 없는 10%를 책임집니다.)

4. 타입이 지정된 도구 (Typed tools)가 아니면 의미가 없습니다.

모든 도구는 JSON 스키마 (JSON schema)를 입력받고 구조화된 데이터 (structured data)를 반환합니다. "쿼리 문자열 (query string)을 넘겨주면 된다" 식의 탈출구는 없습니다. 이는 단순히 개발자의 위생 (hygiene) 문제가 아닙니다. 타입 시스템 (type system)은 대부분의 에이전트 실수가 실제 쓰기 작업으로 이어지기 전에 포착되는 가드레일 (guardrail)이기 때문입니다.

TOTAL_SPENDINGS GTE "five hundred"와 같은 조건으로 create_segment를 호출하는 에이전트는 즉시 유효성 검사 (validation)에 실패합니다. 자유 형식 텍스트 (free-text) 인터페이스를 통해 동일한 실수를 저지르면, 아무도 타겟팅되지 않거나 혹은 최악의 경우 모든 사람을 타겟팅하게 되는, 조용히 비어 있는 세그먼트 (segment)가 생성됩니다.

자유 형식 텍스트 도구 입력은 에이전트의 '느낌 (vibes)'을 디버깅하게 만드는 지름길입니다. 스키마 (Schema)는 그 '느낌'을 400 에러로 바꿔줍니다.

5. 실제로 확인하게 되는 감사 추적 (audit trail)

모든 에이전트 호출은 무엇이, 어떤 인자 (arguments)와 함께, 어떤 토큰에 의해, 언제 호출되었는지를 기록합니다. 각 토큰은 lastUsedAt을 보여줍니다. 지루하고 표준적이며, 모두가 건너뛰는 부분입니다.

에이전트에게 이것이 특히 중요한 이유는 다음과 같습니다. 인간 운영자의 경우, 이상한 행동은 행동을 수행하는 인간에 의해 감지됩니다. 하지만 에이전트는 스스로의 오작동을 감지하지 못합니다. 새벽 3시에 14개의 동일한 세그먼트를 생성하는 재시도 루프 (retry loop)는 무언가가 기록하고 있지 않는 한 보이지 않습니다. 그리고 어떤 돌발 상황 이후 상인의 첫 번째 질문은 "에이전트가 정확히 무엇을 했나요?"가 될 것입니다. 여러분은 그 답변이 어깨를 으쓱하는 태도가 아니라, 로그 라인 (log line)이기를 원할 것입니다.

체크리스트

에이전트 앞에 — 어떤 에이전트든, 어떤 백엔드든 — 쓰기 도구 (write tools)를 배치하려 한다면, 이것이 전체 목록입니다:

  1. 읽기 전용 (Read-only) 기본 설정, 쓰기는 토큰별로 선택적 허용 (opt-in)
  2. 에이전트가 제어하는 모든 숫자에 대해 스키마 내 하드 천장 (Hard ceilings) 설정
  3. 가역성 (reversibility)에 따라 쓰기 작업 정렬 — 되돌릴 수 없으며 고객에게 노출되는 작업은 초안 (draft) 단계에서 종료되어야 하며, 절대 바로 전송 (send)되지 않아야 함
  4. 모든 곳에 타입이 지정된 입력 (Typed inputs) 사용, 자유 형식 텍스트 탈출구 금지
  5. 모든 호출을 기록, lastUsedAt을 노출하여 감사 추적 (audit trail)이 "무엇을 했는가?"에 답할 수 있게 할 것

이 중 영리한 것은 하나도 없습니다. 그것이 바로 핵심입니다. 안전한 에이전트 인터페이스와 무서운 인터페이스의 차이는 연구 수준의 정렬 (alignment) 작업이 아니라, 여러분의 새로운 API 소비자(에이전트)가 일정 비율의 확률로 자신 있게 틀릴 수 있다는 가정하에 적용되는, 우리가 이미 인간용 API를 위해 수행하고 있는 지루한 엔지니어링과 같습니다.

(해당 스토어 인터페이스는 FavCRM의 Shopify 앱 뒤에 있는 MCP 서버이며, 약 20개의 도구(tools)를 포함하고, 위에서 언급한 제한된 쓰기 권한 외에 읽기 권한을 가집니다. 이러한 패턴은 이 사례에만 국한된 것이 아닙니다.)

여전히 확신이 서지 않는 부분: 제한(caps)과 초안(drafts) 방식은 '알려진' 실패 모드(failure modes)를 처리합니다. 저를 걱정하게 만드는 것은 느린 실패입니다. 즉, 에이전트가 개별적으로는 합리적인 50개의 작은 쓰기 작업을 수행하여, 단일 가드레일(guardrail)로는 잡아낼 수 없는 엉망진창인 상태를 만드는 경우입니다. 속도 제한(Rate limits)이 도움이 되긴 하지만, 그것이 완전한 해결책이라고 생각하지는 않습니다.

여러분의 스택에서 에이전트를 위한 쓰기 권한의 경계선을 어디에 설정하시나요? 그리고 '수천 번의 유효한 호출로 인한 죽음(death-by-a-thousand-valid-calls)' 문제에 대한 좋은 패턴을 찾은 분이 계신가요?

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0