당신의 MCP 서버 클라이언트는 언어 모델입니다. 계약을 진심으로 작성하세요.
요약
AI 에이전트가 API를 호출할 때, 인간 개발자와 달리 도구 설명과 스키마를 기반으로 즉각적이고 반복적인 행동을 합니다. 따라서 API 설계 시 모호성을 제거하고 서버 측의 엄격한 '계약(Contract)'을 확립하는 것이 중요합니다.
핵심 포인트
- API는 문서가 아닌, 실행 가능한 계약이어야 한다.
- JSON Schema를 활용하여 모든 제약을 강제해야 한다 (열거형, 패턴 등).
- 출력은 구조화된 JSON 형식에 전념하고, 산문(prose)을 최소화해야 한다.
- 하위 호환성 규율을 지켜 파괴적인 변경을 방지해야 한다.
Model Context Protocol은 모든 내부 스크립트, 데이터베이스 쿼리 및 관리자 작업을 API로 전환했습니다.
그 API의 클라이언트는 문서를 읽는 개발자가 아닙니다. 그것은 실행 시점에 당신의 도구 설명을 읽고 스스로 무엇을 호출하고 어떤 인수를 사용할지 결정하는 언어 모델입니다.
인간 개발자가 당신의 API를 통합할 때, 모호함은 그들의 판단에 흡수됩니다. 그들은 문서를 두 번 읽고, 스테이징 환경에서 테스트하며, 409 에러가 말이 안 될 때 이메일을 보냅니다. 하지만 에이전트는 그런 것을 전혀 하지 않습니다. 그것은 도구의 이름, 설명 및 입력 스키마를 읽고, 해당 도구가 무엇을 하는지에 대한 믿음을 형성한 다음, 프로덕션 환경에서 즉시, 시간당 수백 번에 걸쳐 그 믿음에 따라 행동합니다.
인간이 사용하는 API의 모든 모호함은 지원 스레드 비용으로 돌아옵니다. 에이전트가 사용하는 도구의 모든 모호함은 _에이전트가 의미한다고 결정한 것_을, 동일한 분기점에 도달하는 모든 세션 수만큼 곱한 비용으로 돌아옵니다.
해결책은 클라이언트 측에서 더 나은 프롬프팅이 아닙니다. 클라이언트 프롬프트는 당신이 통제할 수 없는 유일한 것입니다. 해결책은 서버 측의 계약입니다.
소비자가 에이전트일 때 무엇이 바뀌는가
| 계약 요소 | 인간 개발자 | AI 에이전트 |
|---|---|---|
| 문서화 | 통합 중 한 번 읽기 | 매 세션마다 스키마에서 재읽기; 설명 자체가 문서임 |
| ... | ||
| 없습니다. 이것은 이국적인 것이 아닙니다. 이미 좋은 OpenAPI 사양이 요구하는 것과 같은 엄격함입니다. 다만, 독자가 완벽한 인내심을 가지고 있고, 스키마 외의 컨텍스트가 전혀 없으며, 명확히 질문할 능력이 없다고 가정하고 적용된 것입니다. |
입력 스키마: 설명하지 말고 제약하세요
JSON Schema는 계약의 강제(enforcement) 계층이며, 에이전트는 산문보다 이를 더 신뢰성 있게 존중합니다. 스키마에서 표현할 수 있는 모든 제약은 스키마에 표현해야 합니다. 자유 텍스트 문자열 대신 열거형(enums)을 사용하고, 식별자에는 패턴을 적용하며, 명시적인 기본값(defaults)을 설정하고, 필수 필드는 진정한 최소한으로 유지해야 합니다.
"amount_cents": {
"type": "integer",
"minimum": 1,
...
반(反)패턴은 수락되는 값들을 설명하는 문단 형식의 산문이 포함된 문자열 타입 필드를 사용하는 것입니다. 에이전트들은 결국 파서가 예상하지 못한 변형을 보내게 될 것입니다.
출력 계약: 에이전트는 구문을 분석하고, 훑어보지 않습니다
친근한 문단 형식의 산문(prose)을 반환하는 MCP 도구는 모든 소비 에이전트가 영어 구문을 분석하도록 강제합니다. 이 구문 분석은 확률적 연산이며 어딘가에서 잘못될 것입니다.
구조화된 출력에 전념하십시오: 안정적인 필드 이름, 기계가 읽을 수 있는 상태(status), 그리고 후속 호출에 에이전트가 사용할 수 있는 식별자(identifier)를 제공해야 합니다. 산문은 부하를 지탱하는 구조 안에 속하는 것이 아니라, 지정된 summary 필드에 포함되어야 합니다.
{
"status": "refunded",
"refund_id": "rf_8104",
...
그리고 공개 API의 하위 호환성(backward-compatibility) 규율을 가지고 출력 필드를 다루십시오. refund_id를 refundId로 이름을 바꾸는 것은 컴파일러가 잡아내지 못하더라도 파괴적인 변경(breaking change)입니다. 왜냐하면 이를 소비하는 에이전트들은 해당 식별자가 필요한 어떤 단계에서든 실패하기 시작할 것이기 때문입니다.
에이전트가 처리할 수 있는 오류 분류 체계 (Error Taxonomy)
사람은 "무언가 잘못되었습니다. 나중에 다시 시도해 주세요"라는 메시지를 읽고 판단을 내립니다. 하지만 에이전트는 그 판단이 인코딩되어야 합니다: 모든 오류는 정확히 하나의 복구 조치(recovery action)에 매핑되어야 합니다.
| 클래스 | 예시 코드 | 에이전트 조치 |
|---|---|---|
| Transient (일시적) | UPSTREAM_TIMEOUT | 백오프(backoff)를 적용하여 재시도하고, 동일한 Idempotency Key 사용 |
| ... | ||
| 두 가지 규칙이 이 분류 체계를 정직하게 유지합니다: |
- 코드는 추가 전용입니다. 에이전트와 그 프롬프트는 코드 주변에 고착화되므로, 이름 변경은 파괴적인 변경(breaking change)입니다.
- 모든 유효성 검사 오류는 실패한 필드의 이름을 구조적으로 상세하게 명시해야 합니다. 단순히 "잘못된 요청(invalid request)"이라고 보내면 에이전트를 추측하고 재시도하는 루프에 빠뜨리며, 이는 귀하의 로그에서 공격과 정확히 똑같이 보입니다.
부작용 (Side effects): 플래그를 지정하지 않으면 에이전트들이 찾아낼 것입니다
모든 도구를 세 가지 방식으로 분류하세요: 읽기 전용(read-only)인지, 변경(mutating)하는지, 항등원적(idempotent)인지 아닌지, 되돌릴 수 있는지(reversible) 또는 파괴적인지(destructive). MCP는 바로 이러한 것을 위한 네이티브 어노테이션인 readOnlyHint, destructiveHint, idempotentHint를 가지고 있으며, 스펙은 기본값으로 설정하기보다는 그 값들을 의도적으로 결정해야 합니다. 클라이언트 애플리케이션은 이 힌트들을 사용하여 인간에게 확인을 요청할 시점을 결정합니다. 잘못 레이블링된 도구는 해당 레이블이 약속한 신뢰도를 그대로 물려받게 됩니다.
에이전트가 재시도할 수 있는 모든 변경(mutating) 도구에는 중복 호출 시 원래 결과를 반환하도록 하는 idempotency_key 입력값이 필요합니다.
진정으로 파괴적인 작업—삭제, 되돌릴 수 없는 전송, 자금 이동—의 경우 가장 강력한 계약 조항은 두 단계 구조입니다. 즉, 무엇이 발생할지(would happen)와 확인 토큰을 반환하는 dry_run 모드와 해당 토큰을 요구하는 실행(execute) 모드를 분리하는 것입니다. 이렇게 하면 '에이전트가 잘못된 것을 삭제했다'는 사고를 거부된 호출로 바꿀 수 있습니다.
종합하기: 환불 도구 계약서
tool: refund_invoice
purpose: 실패했거나 이의 제기된 인보이스를 한 번 환불합니다.
inputs: invoice_id (패턴 inv_*), amount_cents (1..500000),
...
모든 줄은 에이전트가 그렇지 않았다면 추측으로 답했을 질문에 대한 답변입니다. 버전 라인은 장식이 아닙니다. 달러를 센트로 변경하는 것은 에이전트 클라이언트가 감지하거나 생존할 수 없는 정확한 종류의 조용한 의미론적 변화입니다.
에이전트 대상 API에 대한 불편한 진실은 다음과 같습니다: 실제 인터페이스는 코드가 아닙니다. 그것은 계약서가 언어 모델에게 유도하는 믿음입니다. 먼저 계약서를 작성하고, 그 믿음들은 적어도 당신이 선택한 것들로 만들어져야 합니다.
버전 관리/검토 게이트 프로세스와 더 깊은 오류 분류법을 포함한 전체 버전은 spec-coding.dev에서 확인할 수 있습니다. 또한 평이한 언어 설명으로부터 이 계약 구조를 생성하는 무료 브라우저 기반 API 스펙 생성기도 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기