리소스
리소스는 Model Context Protocol (MCP)의 핵심 프리미티브로, 서버가 클라이언트가 읽고 LLM 상호작용의 컨텍스트로 사용할 수 있는 데이터와 콘텐츠를 노출할 수 있게 합니다.
리소스는 애플리케이션 제어 방식으로 설계되었습니다. 즉, 클라이언트 애플리케이션이 리소스를 사용하는 방식과 시기를 결정할 수 있습니다. 다른 MCP 클라이언트는 리소스를 다르게 처리할 수 있습니다. 예를 들어:
- Claude Desktop은 현재 사용자가 리소스를 명시적으로 선택해야 사용할 수 있습니다
- 다른 클라이언트는 휴리스틱을 기반으로 리소스를 자동으로 선택할 수 있습니다
- 일부 구현에서는 AI 모델 자체가 사용할 리소스를 결정하도록 허용할 수도 있습니다
서버 작성자는 리소스 지원을 구현할 때 이러한 상호작용 패턴 중 어떤 것이라도 처리할 준비가 되어 있어야 합니다. 모델에 데이터를 자동으로 노출하려면 서버 작성자는 도구와 같은 모델 제어 프리미티브를 사용해야 합니다.
개요
리소스는 MCP 서버가 클라이언트에 제공하고자 하는 모든 종류의 데이터를 나타냅니다. 다음을 포함할 수 있습니다:
- 파일 내용
- 데이터베이스 레코드
- API 응답
- 실시간 시스템 데이터
- 스크린샷 및 이미지
- 로그 파일
- 기타 등등
각 리소스는 고유한 URI로 식별되며 텍스트 또는 바이너리 데이터를 포함할 수 있습니다.
리소스 URI
리소스는 다음 형식을 따르는 URI를 사용하여 식별됩니다:
[protocol]://[host]/[path]예를 들어:
file:///home/user/documents/report.pdfpostgres://database/customers/schemascreen://localhost/display1
프로토콜과 경로 구조는 MCP 서버 구현에 의해 정의됩니다. 서버는 자체적인 사용자 정의 URI 스킴을 정의할 수 있습니다.
리소스 유형
리소스는 두 가지 유형의 콘텐츠를 포함할 수 있습니다:
텍스트 리소스
텍스트 리소스는 UTF-8 인코딩된 텍스트 데이터를 포함합니다. 다음에 적합합니다:
- 소스 코드
- 구성 파일
- 로그 파일
- JSON/XML 데이터
- 일반 텍스트
바이너리 리소스
바이너리 리소스는 base64로 인코딩된 원시 바이너리 데이터를 포함합니다. 다음에 적합합니다:
- 이미지
- 오디오 파일
- 비디오 파일
- 기타 비텍스트 형식
리소스 발견
클라이언트는 두 가지 주요 방법으로 사용 가능한 리소스를 발견할 수 있습니다:
직접 리소스
서버는 resources/list 엔드포인트를 통해 구체적인 리소스 목록을 노출합니다. 각 리소스는 다음을 포함합니다:
{
uri: string; // 리소스의 고유 식별자
name: string; // 사람이 읽을 수 있는 이름
description?: string; // 선택적 설명
mimeType?: string; // 선택적 MIME 타입
}리소스 템플릿
동적 리소스의 경우, 서버는 클라이언트가 유효한 리소스 URI를 구성하는 데 사용할 수 있는 URI 템플릿을 노출할 수 있습니다:
{
uriTemplate: string; // RFC 6570을 따르는 URI 템플릿
name: string; // 이 유형에 대한 사람이 읽을 수 있는 이름
description?: string; // 선택적 설명
mimeType?: string; // 일치하는 모든 리소스에 대한 선택적 MIME 타입
}리소스 읽기
리소스를 읽기 위해 클라이언트는 리소스 URI와 함께 resources/read 요청을 보냅니다.
서버는 리소스 콘텐츠 목록으로 응답합니다:
{
contents: [
{
uri: string; // 리소스의 URI
mimeType?: string; // 선택적 MIME 타입
// 다음 중 하나:
text?: string; // 텍스트 리소스용
blob?: string; // 바이너리 리소스용 (base64 인코딩)
}
]
}resources/read 요청에 대해 여러 리소스를 반환할 수 있습니다. 예를 들어, 디렉토리를 읽을 때 디렉토리 내의 파일 목록을 반환하는 데 사용할 수 있습니다.리소스 업데이트
MCP는 두 가지 메커니즘을 통해 리소스의 실시간 업데이트를 지원합니다:
목록 변경
서버는 사용 가능한 리소스 목록이 변경될 때 notifications/resources/list_changed 알림을 통해 클라이언트에 알릴 수 있습니다.
콘텐츠 변경
클라이언트는 특정 리소스에 대한 업데이트를 구독할 수 있습니다:
- 클라이언트가 리소스 URI와 함께
resources/subscribe전송 - 리소스가 변경될 때 서버가
notifications/resources/updated전송 - 클라이언트는
resources/read로 최신 콘텐츠 가져올 수 있음 - 클라이언트는
resources/unsubscribe로 구독 해지할 수 있음
구현 예제
MCP 서버에서 리소스 지원을 구현하는 간단한 예제입니다:
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
resources: {}
}
});
// 사용 가능한 리소스 목록
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain"
}
]
};
});
// 리소스 콘텐츠 읽기
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === "file:///logs/app.log") {
const logContents = await readLogFile();
return {
contents: [
{
uri,
mimeType: "text/plain",
text: logContents
}
]
};
}
throw new Error("Resource not found");
}); app = Server("example-server")
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="file:///logs/app.log",
name="Application Logs",
mimeType="text/plain"
)
]
@app.read_resource()
async def read_resource(uri: AnyUrl) -> str:
if str(uri) == "file:///logs/app.log":
log_contents = await read_log_file()
return log_contents
raise ValueError("Resource not found")
# 서버 시작
async with stdio_server() as streams:
await app.run(
streams[0],
streams[1],
app.create_initialization_options()
)모범 사례
리소스 지원을 구현할 때:
- 명확하고 설명적인 리소스 이름과 URI 사용
- LLM 이해를 돕기 위한 유용한 설명 포함
- 알려진 경우 적절한 MIME 타입 설정
- 동적 콘텐츠를 위한 리소스 템플릿 구현
- 자주 변경되는 리소스에는 구독 사용
- 명확한 오류 메시지로 오류를 우아하게 처리
- 큰 리소스 목록에는 페이지네이션 고려
- 적절한 경우 리소스 콘텐츠 캐시
- 처리 전 URI 검증
- 사용자 정의 URI 스킴 문서화
보안 고려사항
리소스를 노출할 때:
- 모든 리소스 URI 검증
- 적절한 액세스 제어 구현
- 디렉토리 순회를 방지하기 위해 파일 경로 정화
- 바이너리 데이터 처리에 주의
- 리소스 읽기에 대한 속도 제한 고려
- 리소스 액세스 감사
- 전송 중인 민감한 데이터 암호화
- MIME 타입 검증
- 장기 실행 읽기에 대한 타임아웃 구현
- 리소스 정리를 적절하게 처리