본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 4장: 서버 프리미티브 - 도구, 리소스, 프롬프트
2026년 1월 27일·AI / ML·

4장: 서버 프리미티브 - 도구, 리소스, 프롬프트

MCP 서버가 제공하는 세 가지 핵심 프리미티브의 스키마 정의, 구현 패턴, 실전 활용 사례를 상세히 다룹니다.

24분632자8개 섹션
mcptypescriptpython
공유
mcp-guide4 / 10
12345678910
이전3장: 전송 계층 - stdio와 Streamable HTTP다음5장: TypeScript로 MCP 서버 구축하기

프리미티브란 무엇인가

MCP 서버는 클라이언트에 기능을 노출하기 위해 세 가지 프리미티브(Primitive)를 사용합니다. 각 프리미티브는 제어 주체와 용도가 명확히 구분되어 있습니다.

프리미티브제어 주체용도비유
도구(Tools)LLM이 결정작업 수행API 엔드포인트
리소스(Resources)애플리케이션이 선택데이터 제공파일 시스템
프롬프트(Prompts)사용자가 선택워크플로우 템플릿명령 팔레트

이 구분은 단순한 분류가 아니라 설계 원칙입니다. 도구는 부작용(side effect)이 있을 수 있지만, 리소스는 읽기 전용으로 부작용이 없어야 합니다. 프롬프트는 여러 도구와 리소스를 조합한 상위 수준의 워크플로우를 정의합니다.

도구(Tools) 심층 분석

도구의 정의

도구는 MCP 서버가 제공하는 실행 가능한 함수입니다. LLM이 사용자의 요청을 분석하여 적절한 도구를 선택하고, 필요한 매개변수를 구성하여 호출합니다.

도구는 JSON Schema로 입력 매개변수를 정의합니다. 이 스키마는 LLM이 올바른 매개변수를 구성하는 데 사용되며, 서버 측에서 입력 유효성 검사에도 활용됩니다.

도구 스키마

도구 정의 예시
json
{
  "name": "create_issue",
  "description": "GitHub 리포지토리에 새 이슈를 생성합니다. 버그 리포트, 기능 요청, 질문 등을 이슈로 등록할 때 사용합니다.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "owner": {
        "type": "string",
        "description": "리포지토리 소유자의 GitHub 사용자명"
      },
      "repo": {
        "type": "string",
        "description": "리포지토리 이름"
      },
      "title": {
        "type": "string",
        "description": "이슈 제목"
      },
      "body": {
        "type": "string",
        "description": "이슈 본문 (Markdown 형식)"
      },
      "labels": {
        "type": "array",
        "items": { "type": "string" },
        "description": "이슈에 붙일 라벨 목록"
      }
    },
    "required": ["owner", "repo", "title"]
  }
}
Info

도구의 description은 LLM이 도구를 선택할 때 참고하는 핵심 정보입니다. 어떤 상황에서 이 도구를 사용해야 하는지 명확히 기술해야 합니다. 단순히 "이슈를 생성합니다"보다 "GitHub 리포지토리에 새 이슈를 생성합니다. 버그 리포트, 기능 요청, 질문 등을 이슈로 등록할 때 사용합니다"처럼 구체적으로 작성하는 것이 효과적입니다.

도구 호출과 결과

클라이언트가 tools/call 메서드로 도구를 호출하면, 서버는 작업을 수행하고 결과를 반환합니다.

도구 결과 반환
typescript
server.tool(
  "analyze_code",
  "코드를 분석하여 개선 사항을 제안합니다",
  {
    code: { type: "string", description: "분석할 코드" },
    language: { type: "string", description: "프로그래밍 언어" },
  },
  async (params) => {
    const analysis = await performAnalysis(params.code, params.language);
 
    return {
      content: [
        {
          type: "text",
          text: analysis.summary,
        },
        {
          type: "text",
          text: "상세 분석 결과:\n" + analysis.details,
        },
      ],
    };
  }
);

도구 결과는 content 배열로 반환되며, 각 항목은 텍스트, 이미지, 또는 임베디드 리소스일 수 있습니다.

다양한 콘텐츠 유형
typescript
// 텍스트 콘텐츠
{ type: "text", text: "분석 결과입니다" }
 
// 이미지 콘텐츠
{ type: "image", data: "base64EncodedData...", mimeType: "image/png" }
 
// 임베디드 리소스
{
  type: "resource",
  resource: {
    uri: "file:///path/to/result.json",
    text: '{"key": "value"}',
    mimeType: "application/json"
  }
}

도구 설계 원칙

실전에서 효과적인 도구를 설계하기 위한 원칙을 정리합니다.

원자적 작업 단위로 설계합니다. 하나의 도구는 하나의 작업만 수행해야 합니다. "이슈를 생성하고 담당자를 배정하는" 도구보다 "이슈를 생성하는" 도구와 "담당자를 배정하는" 도구를 분리하는 것이 좋습니다. LLM이 필요에 따라 조합하여 사용할 수 있기 때문입니다.

매개변수 설명을 구체적으로 작성합니다. LLM은 매개변수의 description을 보고 어떤 값을 전달해야 하는지 판단합니다. "쿼리"보다 "SQL SELECT 쿼리. WHERE 절을 포함할 수 있으며, 테이블 이름은 스키마에서 확인하세요"처럼 상세하게 기술합니다.

에러 상황을 명시적으로 처리합니다. 도구 실행이 실패할 경우, isError: true와 함께 사용자가 이해할 수 있는 에러 메시지를 반환합니다.

에러 처리 패턴
typescript
server.tool("delete_file", "파일을 삭제합니다", {
  path: { type: "string", description: "삭제할 파일의 절대 경로" },
}, async (params) => {
  try {
    const fs = await import("fs/promises");
    await fs.unlink(params.path);
    return {
      content: [{ type: "text", text: params.path + " 파일이 삭제되었습니다." }],
    };
  } catch (error) {
    return {
      content: [{
        type: "text",
        text: "파일 삭제에 실패했습니다: " + (error as Error).message,
      }],
      isError: true,
    };
  }
});

도구 주석(Annotations)

2025년 6월 사양부터 도구에 주석(Annotations)을 추가할 수 있습니다. 주석은 도구의 동작 특성을 메타데이터로 제공하여, 클라이언트가 도구를 적절히 표시하거나 제어하는 데 활용됩니다.

도구 주석
typescript
server.tool(
  "send_email",
  "이메일을 발송합니다",
  { to: { type: "string" }, subject: { type: "string" }, body: { type: "string" } },
  async (params) => {
    // 이메일 발송 로직
    return { content: [{ type: "text", text: "이메일이 발송되었습니다." }] };
  },
  {
    annotations: {
      destructive: true,     // 되돌릴 수 없는 작업
      readOnlyHint: false,   // 읽기 전용이 아님
      idempotent: false,     // 동일 입력으로 재실행 시 다른 결과 가능
      openWorld: true,       // 외부 시스템과 상호작용
    }
  }
);
주석설명기본값
destructive되돌릴 수 없는 변경을 수행하는지true
readOnlyHint읽기 전용 작업인지false
idempotent동일 입력에 동일 결과를 보장하는지false
openWorld외부 세계와 상호작용하는지true

클라이언트는 이 주석을 활용하여, 예를 들어 destructive: true인 도구를 호출하기 전에 사용자에게 확인을 요청할 수 있습니다.

리소스(Resources) 심층 분석

리소스의 정의

리소스는 MCP 서버가 제공하는 읽기 전용 데이터입니다. 각 리소스는 고유한 URI로 식별되며, 텍스트 또는 바이너리 데이터를 포함합니다.

도구와의 핵심적인 차이점은 다음과 같습니다.

  • 리소스는 부작용이 없습니다. 읽기만 하며, 외부 상태를 변경하지 않습니다.
  • 리소스는 애플리케이션이 선택합니다. LLM이 아닌 호스트 애플리케이션이 어떤 리소스를 컨텍스트에 포함할지 결정합니다.
  • 리소스는 데이터를 제공합니다. 도구가 "행동"이라면, 리소스는 "정보"입니다.

리소스 유형

리소스는 두 가지 방식으로 노출됩니다.

직접 리소스(Direct Resources): 고정된 URI로 접근하는 구체적인 데이터입니다.

직접 리소스 등록
typescript
server.resource(
  "project-config",
  "file:///project/config.json",
  { description: "프로젝트 설정 파일", mimeType: "application/json" },
  async () => ({
    contents: [{
      uri: "file:///project/config.json",
      text: JSON.stringify(projectConfig, null, 2),
      mimeType: "application/json",
    }],
  })
);

리소스 템플릿(Resource Templates): URI 패턴을 사용하여 동적 리소스를 정의합니다. 클라이언트가 패턴에 맞는 URI를 구성하여 요청합니다.

리소스 템플릿
typescript
server.resourceTemplate(
  "db-record",
  "db:///{table}/{id}",
  { description: "데이터베이스 레코드", mimeType: "application/json" },
  async (uri, params) => {
    const record = await db.query(
      "SELECT * FROM " + params.table + " WHERE id = " + params.id
    );
    return {
      contents: [{
        uri: uri.toString(),
        text: JSON.stringify(record),
        mimeType: "application/json",
      }],
    };
  }
);

리소스 구독

클라이언트는 리소스의 변경 사항을 구독할 수 있습니다. 서버가 resources 능력에 subscribe: true를 선언한 경우에만 사용 가능합니다.

이 메커니즘은 설정 파일이 변경되거나, 데이터베이스 레코드가 업데이트되는 등의 상황에서 유용합니다.

리소스 활용 패턴

실전에서 리소스가 효과적으로 활용되는 패턴을 소개합니다.

프로젝트 컨텍스트 제공: 프로젝트의 구조, 설정, 규칙 등을 리소스로 노출하여 LLM이 프로젝트를 이해하는 데 필요한 컨텍스트를 제공합니다.

프로젝트 컨텍스트 리소스
typescript
server.resource(
  "project-structure",
  "project://structure",
  { description: "프로젝트 디렉토리 구조" },
  async () => ({
    contents: [{
      uri: "project://structure",
      text: await generateDirectoryTree("./src"),
    }],
  })
);
 
server.resource(
  "coding-standards",
  "project://standards",
  { description: "팀 코딩 표준 및 규칙" },
  async () => ({
    contents: [{
      uri: "project://standards",
      text: await readFile("./.cursor/rules.md"),
    }],
  })
);

데이터 탐색: 데이터베이스 스키마, 테이블 목록, 최근 로그 등을 리소스로 노출합니다.

데이터베이스 스키마 리소스
typescript
server.resource(
  "db-schema",
  "db://schema",
  { description: "데이터베이스 전체 스키마" },
  async () => ({
    contents: [{
      uri: "db://schema",
      text: await getSchemaDefinition(),
      mimeType: "application/json",
    }],
  })
);

프롬프트(Prompts) 심층 분석

프롬프트의 정의

프롬프트는 MCP 서버가 제공하는 재사용 가능한 워크플로우 템플릿입니다. 단순한 문자열 템플릿이 아니라, 동적으로 생성되는 구조화된 메시지 시퀀스입니다.

프롬프트의 핵심 가치는 전문 지식의 캡슐화에 있습니다. 예를 들어 "코드 리뷰" 프롬프트는 코드 리뷰에 필요한 관점(보안, 성능, 가독성 등)을 미리 정의하고, 리뷰 대상 코드를 매개변수로 받아 완성된 리뷰 요청을 생성합니다.

프롬프트 정의와 사용

프롬프트 등록
typescript
server.prompt(
  "code-review",
  "코드 리뷰를 수행합니다",
  [
    { name: "code", description: "리뷰할 코드", required: true },
    { name: "language", description: "프로그래밍 언어", required: false },
    { name: "focus", description: "집중할 영역 (security/performance/readability)", required: false },
  ],
  async (params) => ({
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: [
            "다음 " + (params.language || "") + " 코드를 리뷰해 주세요.",
            params.focus ? "특히 " + params.focus + "에 집중해 주세요." : "",
            "",
            "리뷰 관점:",
            "1. 버그 가능성",
            "2. 보안 취약점",
            "3. 성능 개선 여지",
            "4. 코드 가독성",
            "5. 모범 사례 준수 여부",
            "",
            "코드:",
            "```" + (params.language || ""),
            params.code,
            "```",
          ].filter(Boolean).join("\n"),
        },
      },
    ],
  })
);

클라이언트는 prompts/list로 사용 가능한 프롬프트를 조회하고, prompts/get으로 특정 프롬프트를 매개변수와 함께 요청합니다.

프롬프트에 리소스 포함하기

프롬프트는 임베디드 리소스를 포함할 수 있습니다. 이를 통해 관련 데이터를 자동으로 컨텍스트에 포함시킬 수 있습니다.

리소스를 포함한 프롬프트
typescript
server.prompt(
  "debug-error",
  "에러를 분석하고 수정 방안을 제시합니다",
  [
    { name: "error_message", description: "에러 메시지", required: true },
  ],
  async (params) => {
    const recentLogs = await getRecentLogs();
    const config = await getAppConfig();
 
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: "다음 에러를 분석하고 수정 방안을 제시해 주세요.\n\n에러: " + params.error_message,
          },
        },
        {
          role: "user",
          content: {
            type: "resource",
            resource: {
              uri: "app://logs/recent",
              text: recentLogs,
              mimeType: "text/plain",
            },
          },
        },
        {
          role: "user",
          content: {
            type: "resource",
            resource: {
              uri: "app://config",
              text: JSON.stringify(config, null, 2),
              mimeType: "application/json",
            },
          },
        },
      ],
    };
  }
);

이 프롬프트를 실행하면, 에러 메시지와 함께 최근 로그와 애플리케이션 설정이 자동으로 컨텍스트에 포함됩니다. 사용자가 일일이 관련 정보를 수집하여 붙여넣을 필요가 없습니다.

클라이언트 프리미티브

서버 프리미티브 외에 클라이언트 프리미티브도 존재합니다. 이들은 서버가 클라이언트에 요청하는 역방향 기능입니다.

루트(Roots)

루트는 클라이언트가 서버에 알려주는 작업 경계입니다. 서버가 접근할 수 있는 파일 시스템 경로나 URI 범위를 정의합니다.

루트 정보
json
{
  "roots": [
    { "uri": "file:///home/user/project", "name": "메인 프로젝트" },
    { "uri": "file:///home/user/config", "name": "설정 디렉토리" }
  ]
}

루트를 설정하면 서버는 지정된 경계 내에서만 작업을 수행합니다. 이는 보안 측면에서 중요하며, 서버가 의도하지 않은 파일에 접근하는 것을 방지합니다.

샘플링(Sampling)

샘플링은 서버가 클라이언트에 LLM 호출을 요청하는 기능입니다. 서버가 직접 LLM API를 호출하지 않고, 클라이언트의 LLM을 활용할 수 있습니다.

이 기능은 서버가 복잡한 작업을 수행하면서 중간 단계에서 AI 추론이 필요한 경우에 유용합니다. 예를 들어 코드 분석 서버가 분석 결과를 자연어로 요약하기 위해 샘플링을 요청할 수 있습니다.

유도(Elicitation)

유도는 서버가 클라이언트를 통해 사용자에게 추가 정보를 요청하는 기능입니다. 2025년 6월 사양에서 추가되었으며, 두 가지 모드를 지원합니다.

폼 모드: JSON Schema를 사용하여 구조화된 입력을 요청합니다.

유도 요청 (폼 모드)
json
{
  "method": "elicitation/create",
  "params": {
    "message": "데이터베이스 연결 정보를 입력해 주세요.",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "host": { "type": "string", "description": "호스트 주소" },
        "port": { "type": "number", "description": "포트 번호" },
        "database": { "type": "string", "description": "데이터베이스 이름" }
      },
      "required": ["host", "database"]
    }
  }
}

URL 모드: 사용자를 외부 URL로 안내합니다. OAuth 인증 등 민감한 정보를 MCP 클라이언트를 거치지 않고 직접 처리해야 하는 경우에 사용합니다.

Warning

유도 기능은 비교적 최근에 추가된 기능으로, 모든 클라이언트가 지원하지 않을 수 있습니다. 서버는 클라이언트의 능력 협상에서 elicitation 능력이 선언되었는지 확인한 후에만 유도를 사용해야 합니다.

프리미티브 조합 전략

실전에서는 세 가지 프리미티브를 조합하여 완성도 높은 MCP 서버를 구축합니다. 다음은 GitHub 통합 서버의 프리미티브 구성 예시입니다.

이 서버에서 사용자가 "코드 리뷰" 프롬프트를 실행하면, 프롬프트가 리소스에서 코드와 PR 정보를 가져오고, LLM이 리뷰를 수행한 후, 도구를 사용하여 리뷰 코멘트를 실제로 작성합니다. 프리미티브들이 유기적으로 연결되어 완전한 워크플로우를 구성하는 것입니다.

정리

이 장에서 다룬 핵심 내용을 요약합니다.

  • 도구(Tools)는 LLM이 호출하는 실행 가능한 함수로, 외부 시스템에 작업을 수행합니다.
  • 리소스(Resources)는 읽기 전용 데이터로, 애플리케이션이 LLM 컨텍스트에 포함할 정보를 제공합니다.
  • 프롬프트(Prompts)는 재사용 가능한 워크플로우 템플릿으로, 사용자가 선택하여 실행합니다.
  • 클라이언트 프리미티브(루트, 샘플링, 유도)는 서버가 클라이언트의 기능을 활용하는 역방향 메커니즘입니다.
  • 실전에서는 세 가지 프리미티브를 조합하여 완성도 높은 통합을 구현합니다.

다음 장 미리보기

5장에서는 TypeScript SDK를 사용하여 실제 MCP 서버를 구축합니다. 프로젝트 설정부터 도구, 리소스, 프롬프트 구현, 테스트, 배포까지 전체 과정을 단계별로 진행하겠습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#mcp#typescript#python

관련 글

AI / ML

5장: TypeScript로 MCP 서버 구축하기

TypeScript SDK를 사용하여 프로젝트 설정부터 도구, 리소스, 프롬프트 구현, 테스트까지 MCP 서버를 구축하는 전 과정을 다룹니다.

2026년 1월 29일·18분
AI / ML

3장: 전송 계층 - stdio와 Streamable HTTP

MCP의 두 가지 핵심 전송 방식인 stdio와 Streamable HTTP의 동작 원리, 장단점, 선택 기준을 상세히 다룹니다.

2026년 1월 25일·17분
AI / ML

6장: Python FastMCP로 서버 구축하기

Python의 FastMCP 프레임워크를 사용하여 데코레이터 기반의 간결하고 직관적인 MCP 서버를 구축하는 방법을 다룹니다.

2026년 1월 31일·17분
이전 글3장: 전송 계층 - stdio와 Streamable HTTP
다음 글5장: TypeScript로 MCP 서버 구축하기

댓글

목차

약 24분 남음
  • 프리미티브란 무엇인가
  • 도구(Tools) 심층 분석
    • 도구의 정의
    • 도구 스키마
    • 도구 호출과 결과
    • 도구 설계 원칙
    • 도구 주석(Annotations)
  • 리소스(Resources) 심층 분석
    • 리소스의 정의
    • 리소스 유형
    • 리소스 구독
    • 리소스 활용 패턴
  • 프롬프트(Prompts) 심층 분석
    • 프롬프트의 정의
    • 프롬프트 정의와 사용
    • 프롬프트에 리소스 포함하기
  • 클라이언트 프리미티브
    • 루트(Roots)
    • 샘플링(Sampling)
    • 유도(Elicitation)
  • 프리미티브 조합 전략
  • 정리
  • 다음 장 미리보기