joshi-chinmay-016/AgentProof

Diretrizes e padrões para definir ferramentas, recursos e prompts em uma aplicação NitroStack com validação de esquema via Zod, incluindo cache, limitação de taxa e uploads de arquivos em base64.

O que é AgentProof?

Esta skill fornece um guia completo e padronizado para desenvolvedores que trabalham com o framework NitroStack para construir servidores MCP (Model Context Protocol). Ela cobre a definição e validação de três componentes essenciais: ferramentas (@Tool), recursos (@Resource) e prompts (@Prompt). Para cada um, são especificadas opções de configuração, uso de esquemas Zod para validação rigorosa de entrada e saída, e exemplos práticos de código TypeScript. Além disso, a skill detalha políticas de controle de execução, como cache de resultados com @Cache para otimização de desempenho e limitação de taxa com @RateLimit para prevenir abusos. Por fim, oferece um tratamento completo para uploads de arquivos, incluindo a declaração de esquemas, a decodificação segura de payloads base64 (suportando tanto formato Data URL quanto raw) e práticas de salvamento seguro em disco com prevenção de ataques de path traversal. É uma referência indispensável para qualquer desenvolvedor que deseje construir aplicações NitroStack robustas, seguras e eficientes.

Funciona com~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 Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Quando Usar

Use esta habilidade sempre que estiver definindo, editando ou validando ferramentas, recursos ou prompts em um servidor MCP NitroStack.

Definindo Ferramentas com @Tool

Uma ferramenta MCP expõe uma função que um cliente de IA pode invocar. Decore um método de serviço ou controlador com @Tool.

Principais Opções da Ferramenta:

  • name: Identificador único em kebab-case ou snake_case.
  • description: Descrição detalhada explicando quando e como o cliente deve usá-la.
  • inputSchema: Um esquema de objeto Zod para validação rigorosa das entradas.
  • outputSchema (opcional): Esquema Zod validando a estrutura de saída.
import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core';

@Controller('weather')
export class WeatherService {
  @Tool({
    name: 'get_current_weather',
    description: 'Obtenha a previsão do tempo atual para uma cidade específica.',
    inputSchema: z.object({
      city: z.string().describe('O nome da cidade, ex: São Paulo'),
      unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
    }),
  })
  @InitialTool() // Invocado automaticamente quando o cliente de IA inicializa/inicia
  async getWeather(
    input: { city: string; unit: 'celsius' | 'fahrenheit' },
    ctx: ExecutionContext
  ) {
    ctx.logger.info(`Buscando clima para ${input.city}`);
    // implementação
    return {
      city: input.city,
      temp: 22,
      condition: 'Ensolarado',
    };
  }
}

Definindo Recursos com @Resource

Um recurso MCP expõe arquivos/URIs de dados estáticos ou dinâmicos que o cliente de IA pode ler.

Principais Opções do Recurso:

  • uri: Padrão de URI (ex: git://{owner}/{repo}/file ou estático app://config).
  • name: Nome único do recurso.
  • description: Explicação sobre quais dados este recurso fornece.
  • mimeType: Tipo MIME da resposta (ex: text/plain, application/json).
import { Resource, ExecutionContext } from '@nitrostack/core';

export class ConfigResources {
  @Resource({
    uri: 'app://settings',
    name: 'Configurações da Aplicação',
    description: 'Configurações e parâmetros de todo o sistema.',
    mimeType: 'application/json',
  })
  async getSettings(ctx: ExecutionContext) {
    return {
      environment: 'development',
      debugMode: true,
    };
  }
}

Definindo Prompts com @Prompt

Um prompt MCP expõe modelos reutilizáveis ou conjuntos de instruções que guiam LLMs.

Principais Opções do Prompt:

  • name: Nome do prompt.
  • description: Descreve qual tarefa este prompt ajuda a realizar.
  • arguments: Declara parâmetros que o cliente pode fornecer para personalizar o modelo do prompt.
import { Prompt, ExecutionContext } from '@nitrostack/core';

export class PromptTemplates {
  @Prompt({
    name: 'code_review',
    description: 'Forneça uma revisão de código intensiva para um trecho de código fornecido.',
    arguments: [
      { name: 'language', description: 'A linguagem de programação, ex: TypeScript', required: true },
      { name: 'code', description: 'O trecho de código a ser revisado', required: true },
    ],
  })
  async getCodeReviewPrompt(
    args: { language: string; code: string },
    ctx: ExecutionContext
  ) {
    return {
      messages: [
        {
          role: 'user',
          content: `Você é um engenheiro de software especialista. Revise este código ${args.language}:

${args.code}`,
        },
      ],
    };
  }
}

Políticas de Ferramentas: Cache (@Cache) e Limitação de Taxa (@RateLimit)

Você pode controlar comportamentos de execução de ferramentas (como otimização de desempenho e limitação) usando decoradores de método.

1. Cache com @Cache

Use @Cache para armazenar em cache as saídas de execução de ferramentas por uma duração especificada (TTL em segundos). Isso reduz a sobrecarga de banco de dados ou API para solicitações idênticas frequentes.

Opções:

  • ttl: Tempo de vida do cache em segundos (obrigatório).
  • key (opcional): Função personalizada (input: any, context?: any) => string que retorna uma chave de cache única com base nas entradas. Se não definida, uma chave é gerada automaticamente a partir dos argumentos de entrada serializados.

Exemplo:

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

export class StationTools {
  @Tool({
    name: 'get_system_status',
    description: 'Busca métricas da estação em tempo real. A resposta é armazenada em cache.',
    inputSchema: z.object({}),
  })
  @Cache({ ttl: 60 }) // Armazena em cache o status por 60 segundos
  async getSystemStatus() {
    return { temperature: 21.5, oxygen: 0.98 };
  }

  @Tool({
    name: 'get_crew_status',
    description: 'Busca o status de um membro da tripulação. Armazenado em cache pelo ID da tripulação.',
    inputSchema: z.object({ id: z.string() }),
  })
  @Cache({
    ttl: 300,
    key: (input) => `crew:status:${input.id}`
  })
  async getCrewStatus(input: { id: string }) {
    // ...
  }
}

2. Limitação de Taxa com @RateLimit

Use @RateLimit para restringir o número de invocações de ferramentas dentro de uma janela de tempo especificada para evitar abusos do cliente.

Opções:

  • requests: Número de solicitações permitidas na janela (obrigatório).
  • window: Duração da janela de limitação (obrigatório). Suporta formatos como '1s', '1m', '1h'.
  • key (opcional): Função personalizada (context: ExecutionContext) => string para agrupar limites de taxa. Útil para limitar a taxa por função de usuário ou chave de API.

Exemplo:

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

export class DiagnosticTools {
  @Tool({
    name: 'run_deep_diagnostic',
    description: 'Executa diagnósticos intensivos. Com limite de taxa.',
    inputSchema: z.object({}),
  })
  @RateLimit({ requests: 3, window: '1m' }) // Máximo de 3 requisições por minuto globalmente
  async runDeepDiagnostic() {
    return { diagnosticReport: 'Todos os sistemas operacionais.' };
  }

  @Tool({
    name: 'request_supply_drop',
    description: 'Solicita suprimentos de inventário. Com limite de taxa por usuário.',
    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) {
    // ...
  }
}

Manipulando Uploads de Arquivos em Ferramentas

O NitroStack suporta uploads de arquivos de clientes MCP (como o NitroStudio) passando o arquivo como uma string codificada em base64 dentro dos parâmetros de entrada de uma ferramenta.

1. Declarando Esquema de Entrada para Uploads de Arquivos

Para aceitar um arquivo enviado, defina três campos Zod no inputSchema da sua ferramenta:

  • file_name: O nome do arquivo (ex: relatorio.csv).
  • file_type: O tipo MIME (ex: text/csv).
  • file_content: A string codificada em base64 contendo os dados do arquivo.
import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';

export class FileTools {
  @Tool({
    name: 'upload_document',
    description: 'Envie um documento de texto ou imagem.',
    inputSchema: z.object({
      file_name: z.string().describe('Nome do arquivo enviado'),
      file_type: z.string().describe('Tipo MIME do arquivo enviado'),
      file_content: z.string().describe('Conteúdo do arquivo codificado em base64'),
    })
  })
  async uploadDocument(input: any, ctx: ExecutionContext) {
    // Lógica de processamento
  }
}

2. Decodificando Payloads Base64

Uploads de arquivos podem chegar em dois formatos, dependendo do cliente:

  1. Formato Data URL: data:image/png;base64,iVBORw0KGgo...
  2. Formato Raw Base64: iVBORw0KGgo...

Use o seguinte padrão de decodificador universal para analisar qualquer formato em um Buffer do Node:

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) {
    // Formato Data URL - decodifica matches[2]
    return Buffer.from(matches[2], 'base64');
  } else {
    // Formato Raw base64 - decodifica a entrada diretamente
    return Buffer.from(content, 'base64');
  }
}

3. Exemplo Seguro de Salvamento de Arquivo

Sempre valide os caminhos de diretório para prevenir ataques de path traversal ao salvar arquivos em disco.

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: 'Decodifica e salva um arquivo enviado de forma segura.',
    inputSchema: z.object({
      file_name: z.string().describe('Nome do arquivo enviado'),
      file_type: z.string().describe('Tipo MIME do arquivo enviado'),
      file_content: z.string().describe('Conteúdo do arquivo codificado em base64'),
    })
  })
  async saveFile(input: any, ctx: ExecutionContext) {
    // Garante que o diretório de uploads existe
    if (!fs.existsSync(UPLOAD_DIR)) {
      fs.mkdirSync(UPLOAD_DIR, { recursive: true });
    }

    // Caminho de destino seguro para prevenir path traversal
    const safeName = path.basename(input.file_name);
    const filePath = path.join(UPLOAD_DIR, safeName);
    if (!filePath.startsWith(UPLOAD_DIR)) {
      throw new Error('Caminho de arquivo inválido detectado (path traversal).');
    }

    // Decodifica e escreve em disco
    const buffer = decodeBase64File(input.file_content);
    fs.writeFileSync(filePath, buffer);

    ctx.logger.info(`Arquivo salvo com sucesso: ${safeName}`);
    return { success: true, path: filePath };
  }
}

Individual skills in this repo

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

Habilidades Relacionadas