MCP Resource로 LLM에게 컨텍스트 읽히기

MCP·에이전트 만들기 2026-07-17 14:35:41 mcpresources개념

글 목록 →

MCP의 3대 primitive 중 Tool은 자주 이야기되지만, Resource는 상대적으로 덜 다뤄진다. 그런데 LLM에게 “지금 이 프로젝트의 상태”나 “이미 쌓여 있는 데이터”를 읽히고 싶을 때 진짜 필요한 건 Tool이 아니라 Resource인 경우가 많다.

Tool과 Resource는 방향이 다르다

가장 헷갈리는 지점부터 정리하자. 둘은 하는 일이 아니라 주도권이 다르다.

  • Tool은 모델이 “이걸 실행해줘”라고 호출하는 동작이다. 부작용(파일 쓰기, API 호출)을 일으키는 게 목적이다.
  • Resource는 클라이언트가 읽어서 컨텍스트에 넣는 데이터다. 원칙적으로 부작용이 없고, GET 요청에 가깝다.

비유하자면 Tool은 함수 호출, Resource는 파일 읽기다. “새 글을 발행해줘”는 Tool이고, “지금까지 발행된 글 목록”은 Resource다. 이 블로그의 MCP 서버도 발행은 publish_post Tool로, 기존 글 조회는 blog://posts Resource로 나눠 두었다.

URI로 노출한다

Resource의 핵심은 각 데이터에 URI를 붙인다는 점이다. blog://posts, file:///README.md, db://users/42처럼 스킴을 자유롭게 정할 수 있다. 클라이언트는 이 URI로 리소스를 식별하고 읽어온다.

리소스는 두 방식으로 제공한다. 목록이 고정적이면 정적으로 나열하고, blog://posts/{slug}처럼 파라미터가 들어가면 템플릿으로 등록한다.

실제 구현

TypeScript SDK로 정적 리소스 하나를 등록하면 이렇다.

server.registerResource(
  "posts",
  "blog://posts",
  {
    title: "발행된 글 목록",
    description: "지금까지 발행된 모든 글의 메타데이터",
    mimeType: "application/json",
  },
  async (uri) => {
    const posts = await loadAllPosts(); // 파일 시스템에서 읽기
    return {
      contents: [
        {
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify(posts, null, 2),
        },
      ],
    };
  }
);

읽기 핸들러는 contents 배열을 돌려준다. 각 항목에는 어떤 URI의 데이터인지, 어떤 형식(mimeType)인지, 그리고 실제 내용(text 또는 바이너리면 blob)이 담긴다.

왜 Tool로 안 하고 Resource로 하나

“글 목록도 그냥 list_posts Tool로 만들면 되지 않나?” 물론 된다. 하지만 구분하는 데는 이유가 있다.

  • 의도가 드러난다. Resource는 “읽기 전용, 부작용 없음”을 프로토콜 수준에서 약속한다. 클라이언트는 안심하고 미리 읽어 컨텍스트에 넣을 수 있다.
  • 클라이언트가 UI를 붙일 수 있다. 많은 MCP 클라이언트가 리소스를 “첨부할 수 있는 항목” 목록으로 보여준다. 사용자가 직접 골라 대화에 끌어다 넣는 식이다.
  • 캐싱·구독이 가능하다. 리소스는 변경 알림(notifications/resources/updated)을 지원해서, 내용이 바뀌면 클라이언트가 다시 읽게 할 수 있다.

정리하면 이렇다. 모델이 세상을 바꾸는 통로는 Tool, 모델에게 세상을 보여주는 창은 Resource다. 서버를 설계할 때 이 동작이 “실행”인지 “읽기”인지부터 물으면, 어느 쪽에 둘지 대개 분명해진다.

댓글

이 글에 대한 의견은 아래 댓글로 남겨주세요 (GitHub 계정 필요). 로그인 없이 남기고 싶다면