LabHub
学习 学习路径 课程

智能体删掉了我的数据库

MCP 规定的内容——三类消息与工具契约

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

한 줄 요약

MCP(Model Context Protocol)는 LLM 애플리케이션과 외부 도구·데이터를 잇는 개방 프로토콜이다. 메시지는 JSON-RPC 2.0 이고, 연결은 상태를 가지며, 양쪽이 능력을 협상한다. 이 세 문장이 스펙 개요 가 "Key Details" 로 적어 둔 전부이고, 나머지는 그 위에 얹힌 약속이다.

概念图: 메시지의 모양 · 호스트 · 클라이언트 · 서버

왜 이게 필요했나

에이전트를 하나 만들면 도구가 붙는다. 데이터베이스를 읽는 도구, 파일을 여는 도구, 사내 API 를 부르는 도구. 처음에는 그 도구를 함수 세 개로 만들고 모델에 설명을 붙이면 끝난다. 문제는 두 번째 에이전트 앱을 만들 때, 그리고 다른 팀의 도구를 가져다 쓸 때 생긴다. 도구의 이름·인자·결과 형식·오류 처리·승인 절차를 앱마다 다시 정해야 하고, 도구 쪽도 앱마다 다른 붙이기 코드를 가진다. N 개의 앱과 M 개의 도구가 N×M 개의 어댑터를 낳는다.

스펙은 이 문제를 언어 서버 프로토콜(LSP)에 빗댄다. 편집기마다 언어 지원을 따로 만들던 시절을 LSP 가 "편집기 ↔ 언어 서버" 한 가지 규약으로 끝낸 것처럼, MCP 는 "LLM 앱 ↔ 도구 서버" 를 한 가지 규약으로 끝내려는 것이다. 그래서 스펙이 정의하는 것은 모델의 동작이 아니라 메시지의 모양이다. 누가 먼저 말하고, 무엇을 답해야 하고, 실패를 어떻게 알리는가.

역할은 셋이다. 호스트(Host)는 연결을 시작하는 LLM 앱이고, 클라이언트는 그 호스트 안에서 서버 하나와 1:1 로 붙는 커넥터이며, 서버는 맥락과 능력을 제공하는 쪽이다. 서버가 낼 수 있는 것은 세 가지 — 리소스(읽을 데이터), 프롬프트 (템플릿), 그리고 이 코스의 주제인 도구(모델이 실행할 함수)다.

어떻게 동작하나

메시지는 세 종류다. 기본 프로토콜 은 요청·응답·알림을 정의한다. 요청은 idmethod 를 갖고, 응답은 같은 idresult 또는 error하나만 싣는다. 알림은 id 가 없고, 받은 쪽은 답해서는 안 된다(MUST NOT). JSON-RPC 2.0 원문은 id 에 null 을 허용하지만 MCP 는 한 발 더 나가 null 을 금지하고, 한 세션 안에서 같은 id 를 다시 쓰는 것도 금지한다.

{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
 "params": {"name": "count_orders", "arguments": {"status": "paid"}}}

연결에는 순서가 있다. 라이프사이클 은 초기화 → 운영 → 종료 세 단계를 정한다. 클라이언트가 initialize 요청에 자기가 지원하는 프로토콜 버전·능력·구현 정보를 담아 보내면, 서버는 자기 버전과 능력(tools, resources, prompts, logging …)과 serverInfo 로 답한다. 서버가 그 버전을 지원하면 같은 버전을, 아니면 자기가 아는 다른 버전을 돌려주고, 클라이언트는 그 버전을 모르면 끊어야 한다(SHOULD). 그 뒤 클라이언트가 notifications/initialized 알림을 보내면 비로소 운영 단계다. 초기화 전에는 양쪽 다 ping 정도 외에는 요청을 보내지 않는 것이 규칙이다.

도구는 두 메서드로 끝난다. 도구 스펙tools/list 는 도구 목록을 돌려주고, 각 도구는 name·description· inputSchema(JSON Schema)를 가진다. 모델은 이 스키마를 읽고 인자를 만든다. tools/callnamearguments 를 받아 content 배열(텍스트·이미지· 오디오·리소스 링크)을 돌려준다. 여기서 중요한 구분이 하나 있다 — 오류가 두 종류다. 모르는 도구 이름이나 잘못된 인자처럼 요청 자체가 틀린 것은 JSON-RPC error(스펙의 예시는 -32602)로 답하고, 도구가 실행되다 실패한 것(API 실패, 비즈니스 규칙 위반)은 result 안에 isError: true 와 설명 텍스트로 답한다. 후자는 모델이 읽고 다시 시도할 수 있어야 하기 때문이다.

전송은 두 가지다. 전송 스펙 의 stdio 는 클라이언트가 서버를 자식 프로세스로 띄워 stdin 으로 요청을 쓰고 stdout 에서 응답을 읽는 방식이다. 메시지는 줄바꿈으로 나뉘고 안에 줄바꿈이 있어서는 안 되며, 서버는 stdout 에 MCP 메시지 외의 것을 써서는 안 되고(MUST NOT), 로그는 stderr 로 쓸 수 있다. Streamable HTTP 는 하나의 엔드포인트가 POST 와 GET 을 받고 필요하면 SSE 로 여러 메시지를 흘리는 방식인데, 로컬에서 돌릴 때는 Origin 헤더를 검증하고 127.0.0.1 에만 묶으라고 못 박는다 — DNS 리바인딩으로 브라우저의 웹 페이지가 내 로컬 MCP 서버를 조종하는 것을 막기 위해서다.

현장에서 만나는 모습

이 코스 제목의 사고는 이렇게 난다. 누군가 사내 DB 를 감싼 MCP 서버를 만들었고, 도구는 run_sql 하나였다. 에이전트가 "테스트 데이터 좀 정리해 줘" 를 받고 DROP TABLE orders 를 만들어 보냈고, 서버는 그것을 실행했다. 모델이 나쁜 것이 아니다. 스펙은 도구를 모델이 고르는 것(model-controlled) 이라 정의하고, 그래서 사람이 거부할 수 있는 자리가 항상 있어야 하며(SHOULD), 서버는 모든 입력을 검증하고 접근 제어를 구현하고 호출 빈도를 제한해야 한다(MUST)고 적는다. 그 문장들이 코드 어디에도 없었던 것이 원인이다.

현장에서 MCP 서버를 볼 때 먼저 확인할 것은 세 가지다. tools/list 가 내는 도구가 얼마나 좁은가(만능 도구가 있으면 사고 예약이다), 파괴적 도구에 확인 절차가 있는가, 그리고 stdout 위생 — 디버그 print 한 줄이 클라이언트의 JSON 파서를 죽여 "서버가 응답하지 않는다" 로 보이는 일이 실제로 잦다. 이 코스의 세 실습이 그 셋을 순서대로 다룬다.

다음 퀴즈에서 확인할 것

메시지 세 종류의 구분(특히 알림에 답하면 안 되는 이유), initialize 의 순서와 버전 협상, 오류 두 종류의 구분, stdio 에서 stdout 과 stderr 의 역할을 묻는다. 전부 이 글에 있는 내용이다.