10장. MCP 와 Prompt·Context Engineering
MCP 서버와 클라이언트의 대화
전화를 걸면 먼저 "여보세요, 누구시죠?"로 서로를 확인하고, "무엇을 도와드릴까요?"를 들은 뒤 용건을 말합니다. MCP 클라이언트와 서버도 인사(initialize), 할 수 있는 일 묻기(tools/list), 일 시키기(tools/call)의 순서로 대화합니다.
1번 클라이언트가 서버 프로그램을 띄우고 initialize 로 서로의 판과 능력을 확인합니다. 2번 tools/list 로 도구 이름, 설명, 인자 스키마를 받습니다. 3번 tools/call 로 도구를 이름과 인자로 부릅니다. 4번 결과(글 묶음, 오류 여부)를 받습니다. 여기서 "여보세요"가 initialize, 용건이 tools/call 입니다.
메시지의 꼴: JSON-RPC
| 메서드 | 하는 일 | 돌려받는 것 |
|---|---|---|
initialize | 판과 능력을 맞춘다 | 서버의 이름, 판, 지원하는 기능 |
tools/list | 도구 목록을 받는다 | 도구마다 이름, 설명, 입력 스키마 |
tools/call | 도구를 부른다 | 결과 내용(content), 오류인지(isError) |
resources/read | 리소스를 읽는다 | 자료의 글 |
import json
# 클라이언트가 서버에 보내는 tools/call 요청의 꼴(JSON-RPC 2.0)
request = {"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {"name": "get_stock", "arguments": {"code": "P-101"}}}
print(json.dumps(request, ensure_ascii=False)){"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {...}} 한 줄이 나옵니다. id 는 요청과 응답을 짝짓는 번호입니다. 파이썬 mcp 패키지의 ClientSession 이 이 메시지를 대신 만들고 읽어 주므로, 우리는 session.call_tool("get_stock", {...}) 만 부릅니다.
주고받는 통로
| 통로 | 생김새 | 쓰는 곳 |
|---|---|---|
| stdio | 클라이언트가 서버를 하위 프로그램으로 띄우고 표준 입출력으로 대화 | 내 PC 안의 서버. 이 장의 실습 |
| HTTP(Streamable HTTP) | 서버가 웹 주소에서 기다리고 클라이언트가 접속 | 여러 사람이 함께 쓰는 원격 서버(13장) |
코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
"jsonrpc": "2.0" | 이 메시지가 JSON-RPC 2.0 판의 약속을 따른다는 표시다 |
"id": 3 | 요청 번호다. 서버는 같은 번호를 붙여 답한다 |
"method": "tools/call" | 서버에게 시킬 일의 이름이다. 여기서는 "도구 부르기" |
"params": {"name": …, "arguments": …} | 일에 필요한 값이다. 부를 도구의 이름과 그 도구에 넘길 인자 |
json.dumps(request, …) | dump string(글로 쏟아 내기)의 줄임으로 본다. 파이썬 사전을 JSON 글 한 줄로 바꾼다 |
ensure_ascii=False | 한글을 \uXXXX 꼴로 바꾸지 말고 그대로 적으라는 옵션이다. ASCII(American Standard Code for Information Interchange)는 영어 글자 중심의 기본 문자표다 |
이 코드가 뜻하는 것
카페의 주문 번호표로 읽어 봅니다. 손님은 "아메리카노, 샷 추가"를 주문하고 번호표 3번을 받습니다. 음료가 나오면 직원이 "3번 손님"을 부릅니다. 손님이 여럿이어도 번호로 자기 음료를 찾습니다. 여기서 번호표가 id, 메뉴 이름이 method 와 name, "샷 추가" 같은 옵션이 arguments 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
request = {...} | 요청 하나를 사전으로 짠다 | 주문서를 쓴다 |
"id": 3 | 요청에 번호를 붙인다 | 번호표 3번 |
"params": {"name": "get_stock", "arguments": {"code": "P-101"}} | 부를 도구와 인자를 적는다 | "재고 찾기, 품목은 P-101" |
json.dumps(..., ensure_ascii=False) | 사전을 글 한 줄로 바꾼다 | 주문서를 주방에 넘기는 꼴로 옮겨 적기 |
결과는 사전과 생김새가 거의 같은 JSON 글 한 줄입니다. 파이썬 사전은 프로그램 안에서만 쓰이고, 다른 프로그램(서버)에 보낼 때는 이렇게 글로 바꿔야 합니다.
작게 줄여서 눈으로 보기
응답이 보낸 순서와 다르게 와도 id 로 짝을 찾는 모습을 봅니다. 두 응답은 설명을 위해 지어낸 것입니다.
import json
# 보낸 요청 두 개. id 로 구분한다
sent = {7: "tools/list", 8: "tools/call"}
# 서버가 돌려준 응답. 순서가 바뀌어 와도 id 로 짝을 찾는다
replies = ['{"jsonrpc": "2.0", "id": 8, "result": {"isError": false}}',
'{"jsonrpc": "2.0", "id": 7, "result": {"tools": []}}']
for line in replies:
msg = json.loads(line)
print(msg["id"], "번 응답 →", sent[msg["id"]], "의 답:", msg["result"])8 번 응답 → tools/call 의 답: {'isError': False}
7 번 응답 → tools/list 의 답: {'tools': []}| 결과 | 읽는 법 |
|---|---|
8 번 응답 → tools/call | 먼저 온 응답은 나중에 보낸 8번 요청의 답이었다. 번호로 짝을 찾았다 |
7 번 응답 → tools/list | 7번 요청(도구 목록)의 답은 뒤에 왔다 |
{'isError': False} | JSON 의 false 가 파이썬의 False 로 읽혔다 |
json.loads 는 load string(글에서 읽어 오기)의 줄임으로 보며, json.dumps 의 반대로 JSON 글을 파이썬 사전으로 바꿉니다. 실제로는 ClientSession 이 이 짝 맞추기를 대신해 줍니다.
주의할 점
- stdio 서버는 표준 출력이 대화 통로라서, 서버 코드에서
print()로 아무 글이나 찍으면 메시지가 깨집니다. 기록은 표준 오류나 파일로 남깁니다. - 교안을 만든 PC 의
mcp2.3.0 에서는 도구 함수가 예외를 내면 클라이언트에 "Error executing tool …"만 가고 예외의 내용은 가지 않았습니다. 모델에게 알려야 할 오류는 예외 대신{"오류": "…"}같은 결과로 돌려줍니다.