서버에서 LLM으로 데이터와 콘텐츠를 노출하기
리소스(Resources)는 Model Context Protocol(MCP)의 핵심 기본 요소로, 서버가 클라이언트가 읽고 LLM 상호작용의 컨텍스트로 사용할 수 있는 데이터와 콘텐츠를 노출할 수 있게 해줍니다.
리소스는 애플리케이션 제어(application-controlled)가 되도록 설계되었습니다. 이는 클라이언트 애플리케이션이 언제 어떻게 리소스를 사용할지 결정할 수 있다는 의미입니다. 다양한 MCP 클라이언트는 리소스를 다르게 처리할 수 있습니다. 예를 들면:
- Claude Desktop은 현재 사용자가 리소스를 사용하기 전에 명시적으로 선택해야 합니다
- 다른 클라이언트는 휴리스틱을 기반으로 자동으로 리소스를 선택할 수 있습니다
- 일부 구현은 AI 모델 자체가 어떤 리소스를 사용할지 결정하도록 허용할 수도 있습니다
서버 개발자는 리소스 지원을 구현할 때 이러한 모든 상호작용 패턴을 처리할 준비가 되어 있어야 합니다. 모델에 자동으로 데이터를 노출하려면 서버 개발자는 도구(Tools)와 같은 모델 제어(model-controlled) 기본 요소를 사용해야 합니다.
개요
리소스는 MCP 서버가 클라이언트에 제공하고자 하는 모든 종류의 데이터를 나타냅니다. 여기에는 다음이 포함될 수 있습니다:
- 파일 내용
- 데이터베이스 레코드
- API 응답
- 라이브 시스템 데이터
- 스크린샷 및 이미지
- 로그 파일
- 기타 등등
각 리소스는 고유한 URI로 식별되며 텍스트 또는 바이너리 데이터를 포함할 수 있습니다.
리소스 URI
리소스는 다음 형식을 따르는 URI를 사용하여 식별됩니다:
[protocol]://[host]/[path]
예를 들면:
file:///home/user/documents/report.pdf
postgres://database/customers/schema
screen://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 서버에서 리소스 지원을 구현하는 간단한 예시입니다:
- TypeScript
- Python
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: "애플리케이션 로그",
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("리소스를 찾을 수 없습니다");
});
app = Server("example-server")
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="file:///logs/app.log",
name="애플리케이션 로그",
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("리소스를 찾을 수 없습니다")
# 서버 시작
async with stdio_server() as streams:
await app.run(
streams[0],
streams[1],
app.create_initialization_options()
)
모범 사례
리소스 지원을 구현할 때:
- 명확하고 설명적인 리소스 이름과 URI 사용
- LLM의 이해를 돕기 위한 유용한 설명 포함
- 알려진 경우 적절한 MIME 타입 설정
- 동적 콘텐츠를 위한 리소스 템플릿 구현
- 자주 변경되는 리소스에 대한 구독 사용
- 명확한 오류 메시지로 오류를 우아하게 처리
- 대규모 리소스 목록에 대한 페이지 매김 고려
- 적절한 경우 리소스 내용 캐싱
- 처리하기 전에 URI 검증
- 사용자 정의 URI 체계 문서화
보안 고려사항
리소스를 노출할 때:
- 모든 리소스 URI 검증
- 적절한 접근 제어 구현
- 디렉토리 트래버설을 방지하기 위해 파일 경로 정제
- 바이너리 데이터 처리에 주의
- 리소스 읽기에 대한 속도 제한 고려
- 리소스 접근 감사
- 전송 중인 민감한 데이터 암호화
- MIME 타입 검증
- 장시간 실행되는 읽기에 대한 타임아웃 구현
- 적절하게 리소스 정리 처리