MCP 프롬프트와 리소스: 도구 호출 외 기능 활용법
tools만 쓰다가 prompts·resources까지 제대로 나눠 쓰는 기준

핵심 요약
- 도구는 모델이, 프롬프트와 리소스는 사용자가 시작한다는 차이가 설계의 기준이 된다.
- 반복 입력하는 지시문은 프롬프트로 템플릿화해 클라이언트 UI에서 고르게 한다.
- 매개변수도 부작용도 없는 읽기 전용 자료는 도구가 아니라 리소스로 노출한다.
- 리소스 URI에 사용자 입력을 직접 결합하면 경로 주입과 권한 문제가 생긴다.
MCP 서버를 만들면 대부분 도구(tools)만 정의하고 끝낸다. 그런데 MCP에는 프롬프트(prompts)와 리소스(resources)라는 서버 기능이 더 있다. 같은 자료를 도구로만 노출하면 "조회 요청 → 결과"를 매번 반복하고, 사용자가 매번 같은 지시문을 입력해야 한다. 세 기능의 역할을 나눠 두면 반복이 줄고 사용 흐름이 안정된다. 이 글은 각 기능을 언제 쓰고, 잘못 쓰면 무엇이 망가지는지 정리했다.
도구·프롬프트·리소스의 역할 구분
| 기능 | 누가 시작하나 | 용도 |
|---|---|---|
| tools | 모델 | 실행·조회 등 부작용이 있거나 매개변수가 필요한 작업 |
| prompts | 사용자(또는 클라이언트 UI) | 반복되는 지시문·워크플로 템플릿 |
| resources | 사용자·클라이언트 | 읽기 전용 자료(문서·설정·데이터) 노출 |
핵심 차이는 시작 주체다. 도구는 모델이 스스로 판단해 호출하지만, 프롬프트와 리소스는 사용자가 골라서 가져오는 표면이다.
프롬프트로 반복 작업을 템플릿화하기
매번 "이 저장소를 읽고 릴리스 노트를 다음 형식으로 써 줘"라고 입력하고 있다면, 그것이 프롬프트로 만들 대상이다. 서버가 프롬프트 목록과 인자(예: 릴리스 버전, 대상 독자)를 노출하면 클라이언트 UI가 선택지로 보여준다.
// 프롬프트 정의 예시
server.prompt(
"release-notes",
{ version: z.string(), audience: z.enum(["dev", "user"]) },
({ version, audience }) => ({
messages: [{
role: "user",
content: { type: "text", text: `${version} 릴리스 노트를 ${audience} 대상으로 작성해 줘.` }
}]
})
);
사용자 주도 프롬프트와 서버 제공 프롬프트
프롬프트는 서버가 목록을 제공하고 사용자가 고르는 모델이다. 따라서 모델이 임의로 실행하는 경로가 없다. 반대로 "모델이 상황에 따라 알아서 써야 하는" 지시문이라면 그건 프롬프트가 아니라 도구 설명이나 시스템 지침에 넣어야 한다. 이 구분을 놓치면 아무도 호출하지 않는 프롬프트가 남는다.
리소스로 읽기 전용 자료 노출하기
자주 참조하는 값(코딩 규칙, 스키마, 환경 목록)은 도구 호출로 가져올 필요가 없다. 리소스로 노출하면 클라이언트가 목록에서 골라 붙이거나 자동으로 참조할 수 있다. 리소스는 URI로 식별되고, 읽기 동작만 가진다.
- 정적 문서 — 팀 코딩 규칙, 온보딩 가이드
- 구조 데이터 — 서비스 카탈로그, 설정 스키마, enum 목록
- 자주 바뀌지 않는 참조표 — 에러 코드, 지역 목록
반대로 매개변수에 따라 달라지는 조회(예: 사용자별 주문 목록)는 리소스가 아니라 도구가 맞다. 리소스 URI에 사용자 입력을 조합하기 시작하면 권한과 캐시가 복잡해진다.
무엇을 도구가 아니라 리소스로 두어야 하나
판단 기준은 "매개변수가 필요한가"와 "부작용이 있는가"다. 둘 다 없으면 리소스, 하나라도 있으면 도구다. 프롬프트는 여기에 "사용자가 반복 입력하는 문장인가"를 더해 판단한다. 이 기준을 표로 고정해 두면 새 기능을 추가할 때마다 어디에 넣을지 헷갈리지 않는다.
잘못된 설계의 흔한 예와 주의점
- 모든 자료를 도구로 노출 — 단순 조회에도 모델 호출 왕복이 생긴다
- 모델이 알아서 써야 할 지시를 프롬프트로 만듦 — 아무도 호출하지 않는다
- 리소스 URI에 사용자 입력을 직접 결합 — 경로 주입과 권한 문제를 부른다
- 리소스에 큰 바이너리를 담음 — 컨텍스트를 낭비한다
기능을 나눠 설계하는 원칙은 도구 자체를 줄이는 MCP 도구 설계 원칙과 이어진다. 서버를 처음 만든다면 TypeScript SDK로 MCP 서버 만들기부터 보는 편이 좋다.
클라이언트 호환성과 서버 기능 지원
프롬프트와 리소스는 클라이언트가 해당 기능을 지원해야 실제로 보인다. 어떤 클라이언트는 도구만 노출하고, 어떤 클라이언트는 리소스를 자동으로 참조한다. 그래서 서버를 설계할 때는 "이 기능이 없어도 핵심 작업이 가능한가"를 먼저 확인한다.
| 기능 | 미지원 클라이언트에서의 대체 |
|---|---|
| prompts | 도구 설명이나 문서에 예시 문장을 적어 둔다 |
| resources | 읽기 전용 조회 도구를 함께 제공한다 |
기능을 중복 제공하면 관리 부담이 늘지만, 핵심 자료에 접근하는 경로가 하나뿐이라면 클라이언트 호환성 문제로 사용 자체가 막힐 수 있다. 중요한 리소스에 한해 얇은 조회 도구를 함께 두는 절충이 실용적이다.
또한 프롬프트와 리소스의 내용은 버전 관리 대상이다. 문구를 바꾸면 사용자 경험이 바로 달라지므로, 변경 이력을 남기고 되돌릴 수 있게 관리한다. 특히 조직 용어나 정책이 담긴 리소스는 잘못된 버전이 배포되지 않도록 리뷰 절차를 두는 편이 좋다.
도입 후 측정할 지표
프롬프트와 리소스를 도입한 뒤에는 실제로 사용되는지 확인해야 한다. 아무도 고르지 않는 프롬프트는 제거 후보이고, 계속 조회되는 리소스는 더 잘게 나눌 수 있다. 반복 입력이 줄었는지도 사용자 관점의 핵심 지표다.
- 프롬프트별 선택 횟수와 완료율
- 리소스 참조 빈도와 크기
- 같은 지시를 반복 입력하는 비율
측정 결과는 기능을 늘리는 근거가 아니라 줄이는 근거로도 쓴다. 쓰이지 않는 표면은 유지 비용과 혼란만 키운다.
결국 세 기능을 나누는 기준은 "누가 시작하고, 매개변수가 필요한가"로 요약된다. 이 기준을 문서에 적어 두면 서버가 커져도 어디에 무엇을 둘지 빠르게 판단할 수 있다.
초보자가 자주 실수하는 포인트
- 모든 자료를 도구로만 노출해 단순 조회에도 모델 왕복을 만든다.
- 모델이 알아서 써야 할 지침을 프롬프트로 만들어 아무도 호출하지 않게 한다.
- 리소스 URI에 사용자 입력을 그대로 붙여 주입 경로를 연다.
- 큰 바이너리나 매번 바뀌는 데이터를 리소스로 담아 컨텍스트를 낭비한다.
체크리스트
- 각 기능을 시작 주체(모델/사용자) 기준으로 분류했다.
- 반복 입력되는 지시문을 프롬프트로 정리했다.
- 매개변수 없는 읽기 전용 자료를 리소스로 노출했다.
- 리소스 URI에 사용자 입력을 직접 결합하지 않았다.
- 모델이 상황에 따라 써야 할 지침은 도구 설명이나 시스템 지침에 두었다.
자주 묻는 질문
프롬프트 기능을 지원하지 않는 클라이언트도 있나?
있다. 클라이언트마다 지원 범위가 다르므로, 프롬프트가 없어도 도구만으로 핵심 작업이 가능하도록 설계하는 편이 안전하다.
리소스와 도구를 같은 데이터에 함께 만들면 안 되나?
가능하지만 중복은 관리 부담이다. 읽기 전용 참조는 리소스로, 매개변수·부작용이 있는 접근만 도구로 두는 것이 기본이다.
참고 자료 · 검증 기준
위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.
이 글은 실제 실무 환경 테스트 및 검증된 기술 자료를 바탕으로 작성되었으며, 환경 변화에 따라 지속적으로 갱신됩니다.