CPN 한국어 자습서 · Introduction to Model Context Protocol
3 · Connecting with MCP clients
Accessing resources
서버에 리소스를 정의했으니, 이제 클라이언트가 그것을 요청할 차례입니다. mcp_client.py에 read_resource 함수 하나를 더합니다 — URI를 받아 서버에 요청하고, MIME 타입에 따라 JSON이면 json.loads로 파싱하고 아니면 평문 그대로 돌려줍니다. 그러면 @ 멘션이 동작합니다.
Stephen Grider · Anthropic 기술 스태프
앞에서 MCP 서버에 리소스 둘을 정의했습니다. 이제 클라이언트가 이 리소스들을 요청할 수 있어야 합니다. 그러려면 MCP 클라이언트에 함수 하나를 더합니다. 이 클라이언트에는 우리 애플리케이션의 나머지 부분이 사용하는 기능이 들어 있고, 그 나머지 코드는 이미 만들어져 있습니다. 즉 프로젝트 어딘가에서 우리가 지금 추가할 이 함수를 호출하게 됩니다.
mcp_client.py를 열고 아래로 내려가 read_resource를 찾습니다. 목표는 특정 리소스를 서버에 요청해 읽어 오고, 돌아온 내용을 그 MIME 타입에 따라 파싱한 뒤 데이터를 돌려주는 것입니다. 인자는 uri 하나입니다 — 서버에서 가져올 리소스의 URI죠.
요청을 만들기 전에, 타입을 깔끔히 맞추려고 파일 맨 위에 import 둘을 추가합니다 — json 모듈, 그리고 pydantic에서 AnyUrl을 가져옵니다. 다시 read_resource 함수로 내려와, await self.session().read_resource(AnyUrl(uri))로 결과를 받습니다. AnyUrl로 감싸는 건 타입을 맞추기 위한 것입니다.
그 결과에서 result.contents[0]을 꺼냅니다. 왜 이렇게 하는지 분명히 해 두면 — 조금 전 인스펙터에서 본 응답이 바로 이 result입니다. result에는 contents 리스트가 있고, 그 안에 원소들이 들어 있는데 우리는 맨 첫 번째만 신경 씁니다. 그 첫 원소에서 MIME 타입을 봅니다 — 어떤 종류의 데이터를 받았는지 알려 주기 때문입니다. JSON이면 텍스트를 JSON으로 파싱해 돌려줘야 합니다.
그래서 if isinstance(resource, types.TextResourceContents) and resource.mimeType == "application/json": 분기를 둡니다. 이게 우리 힌트입니다 — 서버가 JSON을 돌려준다고 알려 줬다면, 텍스트 내용을 JSON으로 파싱해야 합니다. 이 경우엔 return json.loads(resource.text)를 돌려줍니다. 그 if에 걸려 일찍 반환하지 않으면, 그냥 return resource.text — 평문 그대로 돌려줍니다. 이건 단일 문서의 내용을 받는 경우에 해당합니다.
이걸로 read_resource가 완성됐습니다. 다시 강조하면, MCP 클라이언트에 우리가 쓰는 코드는 이 코드베이스의 여러 다른 곳에서 호출됩니다. 어딘가에서 방금 만든 이 함수를 불러 문서 이름 목록을 받고, 또 단일 문서 내용을 받아 프롬프트에 넣습니다. 나머지 작업은 이미 끝나 있으므로, 이 시점에서 사실상 전부 동작할 것입니다.
터미널로 돌아가 CLI 앱을 다시 실행해 멘션 기능을 테스트합니다. @를 입력하면 리소스 목록이 보이고, 화살표 키로 스크롤할 수 있습니다. 원하는 리소스에서 멘션을 삽입하면 "@report.pdf 문서에 뭐가 들어 있어?"처럼 됩니다. 이제 그 문서의 내용이 프롬프트에 담겨 Claude로 전송됩니다. 제출하면 즉시 응답이 오고, report.pdf 안에 무엇이 있는지 알려 줍니다. 이번엔 Claude가 문서 내용을 읽으려고 도구를 쓰지 않았습니다 — 우리가 미리 내용을 넣어 줬으니까요. 정리하면, 리소스는 MCP 서버에서 일정량의 정보를 노출하는 데 씁니다.
이 장에서 배우는 것What you'll learn
약 6분클라이언트에 read_resource(uri) 함수 하나를 추가
import — json 모듈 · pydantic AnyUrl
result = await self.session().read_resource(AnyUrl(uri))
result.contents[0] — 첫 콘텐츠만 사용
MIME 분기 — application/json이면 json.loads, 아니면 평문
@ 멘션 → 문서 내용을 프롬프트에 주입 → Claude가 도구 없이 답
read_resource에 넘길 때 타입을 맞춘다.application/json이면 json.loads로 역직렬화, 아니면 text 평문 그대로.서버에 리소스를 정의했으니, 이제 클라이언트가 요청할 수 있어야 합니다. mcp_client.py에 read_resource 함수 하나를 더합니다. 목표는 — URI로 서버에 요청해 내용을 받아 오고, MIME 타입에 따라 파싱한 뒤 데이터를 돌려주는 것입니다.
먼저 타입을 깔끔히 맞추려고 파일 맨 위에 import 둘을 추가합니다 — json 모듈과 pydantic의 AnyUrl입니다.
# mcp_client.py 맨 위 — import 둘 추가 import json from pydantic import AnyUrl
이 클라이언트 코드는 프로젝트의 여러 곳에서 호출됩니다. 어딘가에서 이 함수를 불러 문서 이름 목록을 받고, 또 단일 문서 내용을 받아 프롬프트에 넣습니다. 나머지 연결 코드는 이미 작성돼 있습니다.
await self.session().read_resource(AnyUrl(uri))로 결과를 받고, result.contents[0]에서 첫 콘텐츠를 꺼냅니다. 그 콘텐츠의 mimeType이 application/json이면 텍스트를 json.loads로 역직렬화하고, 그 외엔 text를 평문 그대로 돌려줍니다.
async def read_resource(self, uri: str) -> Any: result = await self.session().read_resource(AnyUrl(uri)) resource = result.contents[0] if isinstance(resource, types.TextResourceContents) and resource.mimeType == "application/json": return json.loads(resource.text) # JSON 힌트 -> 역직렬화 return resource.text # 그 외 -> 평문 그대로
application/json
그 외 (text/plain)
같은 read_resource 한 함수가 들어온 mimeType을 보고 갈라집니다 — application/json이면 json.loads로 역직렬화해 리스트로, 아니면 text를 평문 문자열 그대로 돌려줍니다.
나머지 연결 코드는 이미 끝나 있으므로, 이 시점에서 사실상 전부 동작합니다. CLI에서 @를 입력하면 목록 리소스가 문서 이름을 자동완성으로 띄우고, 문서를 골라 제출하면 그 내용이 프롬프트에 주입되어 Claude가 도구 없이 바로 답합니다.
멘션할 문서 (목록 리소스 · 자동완성)
입력창에 @를 입력하면 목록 리소스가 문서 이름을 자동완성으로 띄웁니다. 문서를 고르고 제출해 보세요.
$ uv run main.py
> what's in the @report.pdf document?
(@ 입력 시 문서 목록 자동완성 -> report.pdf 선택)
[클라이언트] read_resource("docs://documents/report.pdf")
-> contents[0].mimeType == "text/plain" -> 평문 반환
-> 문서 내용을 프롬프트에 주입
Claude: report.pdf는 20미터 응축탑의 상태를 다룬 보고서입니다. ...
(도구 호출 없이 바로 답 — 내용이 이미 프롬프트에 있으므로)read_resource(uri) 추가 — 서버에 요청해 내용을 받아 온다.json · pydantic AnyUrl. 요청은 read_resource(AnyUrl(uri)).result.contents[0]의 mimeType이 application/json이면 json.loads(text), 아니면 text.@ 멘션 → 문서 내용을 프롬프트에 주입 → Claude가 도구 없이 답한다.Q1read_resource에서 받은 콘텐츠의 mimeType이 application/json일 때 무엇을 하나요?
application/json은 "이건 JSON 문자열"이라는 힌트입니다. json.loads로 파싱해 리스트/딕셔너리로 돌려줍니다. 그 외엔 text 그대로입니다.
Q2요청을 만들 때 URI를 무엇으로 감싸 read_resource에 넘기나요?
타입을 맞추려고 AnyUrl(uri)로 감쌉니다. import는 from pydantic import AnyUrl.
Q3@로 문서를 멘션해 제출하면 Claude는 어떻게 답하나요?
멘션된 문서 내용을 미리 프롬프트에 넣으므로, Claude가 도구로 문서를 찾아 읽을 필요가 없습니다.
리소스를 정의(서버)하고 접근(클라이언트)하는 흐름을 모두 완성했습니다. 다음은 슬래시 명령으로 재사용하는 프롬프트 프리미티브입니다. → 프롬프트 정의하기