Community코딩 & 개발github.com

joshi-chinmay-016/AgentProof

Zod 스키마 검증, 캐싱, 속도 제한, Base64 파일 업로드를 포함한 NitroStack 애플리케이션에서 도구, 리소스, 프롬프트를 정의하기 위한 지침 및 패턴입니다.

AgentProof란 무엇인가요?

이 스킬은 NitroStack MCP 서버에서 도구(`@Tool`), 리소스(`@Resource`), 프롬프트(`@Prompt`)를 정의, 편집 및 검증하는 방법에 대한 포괄적인 안내서입니다. Zod를 사용한 엄격한 입력/출력 스키마 검증, `@Cache` 데코레이터로 응답을 캐싱하여 성능 최적화, `@RateLimit` 데코레이터로 클라이언트 호출 속도를 제한하는 방법을 설명합니다. 또한 Base64로 인코딩된 파일 업로드 처리와 디렉터리 트래버설 공격을 방지하는 안전한 파일 저장 패턴을 다룹니다. 이 스킬은 NitroStack 기반으로 MCP 도구를 개발하는 모든 개발자에게 필수적입니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/joshi-chinmay-016/AgentProof/tree/HEAD/agentproof/.agents/skills/tools-resources-prompts

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

사용 시기

이 스킬은 NitroStack MCP 서버에서 도구, 리소스 또는 프롬프트를 정의, 편집 또는 검증할 때마다 사용하세요.

@Tool로 도구 정의하기

MCP 도구는 AI 클라이언트가 호출할 수 있는 함수를 노출합니다. 서비스 또는 컨트롤러 메서드를 @Tool로 장식하세요.

주요 도구 옵션:

  • name: 케밥-케이스 또는 스네이크_케이스의 고유 식별자입니다.
  • description: 클라이언트가 언제, 어떻게 사용해야 하는지 설명하는 상세한 설명입니다.
  • inputSchema: 입력의 엄격한 검증을 위한 Zod 객체 스키마입니다.
  • outputSchema (선택 사항): 출력 구조를 검증하는 Zod 스키마입니다.
import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core';

@Controller('weather')
export class WeatherService {
  @Tool({
    name: 'get_current_weather',
    description: '특정 도시의 현재 일기 예보를 가져옵니다.',
    inputSchema: z.object({
      city: z.string().describe('도시 이름, 예: 서울'),
      unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
    }),
  })
  @InitialTool() // AI 클라이언트 초기화/시작 시 자동 호출
  async getWeather(
    input: { city: string; unit: 'celsius' | 'fahrenheit' },
    ctx: ExecutionContext
  ) {
    ctx.logger.info(`Fetching weather for ${input.city}`);
    // 구현
    return {
      city: input.city,
      temp: 22,
      condition: 'Sunny',
    };
  }
}

@Resource로 리소스 정의하기

MCP 리소스는 AI 클라이언트가 읽을 수 있는 정적 또는 동적 데이터 파일/URI를 노출합니다.

주요 리소스 옵션:

  • uri: URI 패턴 (예: git://{owner}/{repo}/file 또는 정적 app://config).
  • name: 리소스의 고유 이름입니다.
  • description: 이 리소스가 제공하는 데이터에 대한 설명입니다.
  • mimeType: 응답의 MIME 유형 (예: text/plain, application/json).
import { Resource, ExecutionContext } from '@nitrostack/core';

export class ConfigResources {
  @Resource({
    uri: 'app://settings',
    name: '애플리케이션 설정',
    description: '시스템 전체 구성 설정 및 매개변수.',
    mimeType: 'application/json',
  })
  async getSettings(ctx: ExecutionContext) {
    return {
      environment: 'development',
      debugMode: true,
    };
  }
}

@Prompt로 프롬프트 정의하기

MCP 프롬프트는 LLM을 안내하는 재사용 가능한 템플릿 또는 명령어 세트를 노출합니다.

주요 프롬프트 옵션:

  • name: 프롬프트의 이름입니다.
  • description: 이 프롬프트가 어떤 작업을 수행하는 데 도움이 되는지 설명합니다.
  • arguments: 클라이언트가 프롬프트 템플릿을 사용자 지정하기 위해 제공할 수 있는 매개변수를 선언합니다.
import { Prompt, ExecutionContext } from '@nitrostack/core';

export class PromptTemplates {
  @Prompt({
    name: 'code_review',
    description: '주어진 코드 조각에 대한 집중적인 코드 리뷰를 제공합니다.',
    arguments: [
      { name: 'language', description: '프로그래밍 언어, 예: TypeScript', required: true },
      { name: 'code', description: '리뷰할 코드 조각', required: true },
    ],
  })
  async getCodeReviewPrompt(
    args: { language: string; code: string },
    ctx: ExecutionContext
  ) {
    return {
      messages: [
        {
          role: 'user',
          content: `당신은 전문 소프트웨어 엔지니어입니다. 다음 ${args.language} 코드를 리뷰하세요:\n\n${args.code}`,
        },
      ],
    };
  }
}

도구 정책: 캐싱 (@Cache) 및 속도 제한 (@RateLimit)

메서드 데코레이터를 사용하여 도구 실행 동작(성능 최적화 및 제한 등)을 제어할 수 있습니다.

1. @Cache를 사용한 캐싱

@Cache를 사용하여 지정된 기간(TTL(초)) 동안 도구 실행 출력을 캐시합니다. 이는 빈번한 동일 요청에 대한 데이터베이스 또는 API 오버헤드를 줄입니다.

옵션:

  • ttl: 캐시 TTL(초) (필수).
  • key (선택 사항): 입력을 기반으로 고유한 캐시 키를 반환하는 사용자 정의 함수 (input: any, context?: any) => string. 정의되지 않으면 직렬화된 입력 인수에서 키가 자동 생성됩니다.

예시:

import { ToolDecorator as Tool, Cache, z } from '@nitrostack/core';

export class StationTools {
  @Tool({
    name: 'get_system_status',
    description: '실시간 스테이션 메트릭을 가져옵니다. 응답이 캐시됩니다.',
    inputSchema: z.object({}),
  })
  @Cache({ ttl: 60 }) // 60초 동안 상태 캐시
  async getSystemStatus() {
    return { temperature: 21.5, oxygen: 0.98 };
  }

  @Tool({
    name: 'get_crew_status',
    description: '승무원의 상태를 가져옵니다. 승무원 ID별로 캐시됩니다.',
    inputSchema: z.object({ id: z.string() }),
  })
  @Cache({
    ttl: 300,
    key: (input) => `crew:status:${input.id}`
  })
  async getCrewStatus(input: { id: string }) {
    // ...
  }
}

2. @RateLimit을 사용한 속도 제한

@RateLimit을 사용하여 지정된 시간 창 내에서 도구 호출 수를 제한하여 클라이언트 남용을 방지합니다.

옵션:

  • requests: 창에서 허용된 요청 수 (필수).
  • window: 제한 시간 창 (필수). '1s', '1m', '1h'와 같은 형식을 지원합니다.
  • key (선택 사항): 속도 제한을 그룹화하는 사용자 정의 함수 (context: ExecutionContext) => string. 사용자 역할 또는 API 키별로 속도를 제한하는 데 유용합니다.

예시:

import { ToolDecorator as Tool, RateLimit, z, ExecutionContext } from '@nitrostack/core';

export class DiagnosticTools {
  @Tool({
    name: 'run_deep_diagnostic',
    description: '집중 진단을 실행합니다. 속도가 제한됩니다.',
    inputSchema: z.object({}),
  })
  @RateLimit({ requests: 3, window: '1m' }) // 전역적으로 분당 최대 3회 요청
  async runDeepDiagnostic() {
    return { diagnosticReport: 'All systems operational.' };
  }

  @Tool({
    name: 'request_supply_drop',
    description: '인벤토리 보급품을 요청합니다. 사용자별로 속도가 제한됩니다.',
    inputSchema: z.object({ item: z.string() }),
  })
  @RateLimit({
    requests: 5,
    window: '1h',
    key: (ctx: ExecutionContext) => ctx.auth?.subject || 'anonymous'
  })
  async requestSupply(input: { item: string }, ctx: ExecutionContext) {
    // ...
  }
}

도구에서 파일 업로드 처리

NitroStack은 파일을 도구의 입력 매개변수 내에 Base64로 인코딩된 문자열로 전달하여 MCP 클라이언트(예: NitroStudio)의 파일 업로드를 지원합니다.

1. 파일 업로드를 위한 입력 스키마 선언

업로드된 파일을 수락하려면 도구의 inputSchema에 세 개의 Zod 필드를 정의하세요:

  • file_name: 파일 이름 (예: report.csv)
  • file_type: MIME 유형 (예: text/csv)
  • file_content: 파일 데이터를 포함하는 Base64로 인코딩된 문자열입니다.
import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';

export class FileTools {
  @Tool({
    name: 'upload_document',
    description: '텍스트 문서 또는 이미지를 업로드합니다.',
    inputSchema: z.object({
      file_name: z.string().describe('업로드된 파일의 이름'),
      file_type: z.string().describe('업로드된 파일의 MIME 유형'),
      file_content: z.string().describe('Base64로 인코딩된 파일 콘텐츠'),
    })
  })
  async uploadDocument(input: any, ctx: ExecutionContext) {
    // 처리 로직
  }
}

2. Base64 페이로드 디코딩

파일 업로드는 클라이언트에 따라 두 가지 형식으로 도착할 수 있습니다:

  1. 데이터 URL 형식: data:image/png;base64,iVBORw0KGgo...
  2. 원시 Base64 형식: iVBORw0KGgo...

다음 범용 디코더 패턴을 사용하여 두 형식을 모두 Node Buffer로 구문 분석하세요:

import * as fs from 'fs';
import * as path from 'path';

function decodeBase64File(content: string): Buffer {
  const matches = content.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
  
  if (matches && matches.length === 3) {
    // 데이터 URL 형식 - matches[2] 디코딩
    return Buffer.from(matches[2], 'base64');
  } else {
    // 원시 base64 형식 - 입력을 직접 디코딩
    return Buffer.from(content, 'base64');
  }
}

3. 안전한 파일 저장 예시

디스크에 파일을 저장할 때 디렉터리 트래버설 공격을 방지하기 위해 항상 디렉터리 경로를 검증하세요.

import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';
import * as fs from 'fs';
import * as path from 'path';

const UPLOAD_DIR = path.join(process.cwd(), 'uploads');

export class SecureUploadTools {
  @Tool({
    name: 'save_uploaded_file',
    description: '업로드된 파일을 안전하게 디코딩하고 저장합니다.',
    inputSchema: z.object({
      file_name: z.string().describe('업로드된 파일의 이름'),
      file_type: z.string().describe('업로드된 파일의 MIME 유형'),
      file_content: z.string().describe('Base64로 인코딩된 파일 콘텐츠'),
    })
  })
  async saveFile(input: any, ctx: ExecutionContext) {
    // 업로드 디렉터리가 존재하는지 확인
    if (!fs.existsSync(UPLOAD_DIR)) {
      fs.mkdirSync(UPLOAD_DIR, { recursive: true });
    }

    // 경로 트래버설을 방지하기 위한 안전한 대상 경로
    const safeName = path.basename(input.file_name);
    const filePath = path.join(UPLOAD_DIR, safeName);
    if (!filePath.startsWith(UPLOAD_DIR)) {
      throw new Error('유효하지 않은 파일 경로 감지 (경로 트래버설).');
    }

    // 디코딩하여 디스크에 쓰기
    const buffer = decodeBase64File(input.file_content);
    fs.writeFileSync(filePath, buffer);

    ctx.logger.info(`파일 저장 성공: ${safeName}`);
    return { success: true, path: filePath };
  }
}

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

관련 스킬