CommunityProgramación y desarrollogithub.com

joshi-chinmay-016/AgentProof

Directrices y patrones para definir herramientas, recursos y prompts en una aplicación NitroStack con validación de esquemas mediante Zod, incluyendo almacenamiento en caché, limitación de tasa y carga de archivos en base64.

¿Qué es AgentProof?

Esta habilidad proporciona una guía completa para desarrolladores que trabajan con servidores MCP de NitroStack, cubriendo la definición de herramientas, recursos y prompts mediante decoradores. Es especialmente útil para garantizar la seguridad, el rendimiento y la escalabilidad en aplicaciones impulsadas por agentes de IA. Compatible con plataformas como Claude Code, Cursor y Codex, la habilidad detalla cómo usar los decoradores `@Tool`, `@Resource` y `@Prompt` con validación de esquemas Zod, y cómo gestionar políticas de ejecución como el almacenamiento en caché con `@Cache` y la limitación de tasa con `@RateLimit`. También explica el manejo seguro de cargas de archivos mediante codificación base64, incluyendo la desinfección de rutas para prevenir ataques de path traversal. Los patrones presentados permiten a los desarrolladores implementar servidores MCP robustos y eficientes, optimizando la comunicación entre el cliente IA y el backend.

Compatible con~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 Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Cuándo usar

Usa esta habilidad siempre que estés definiendo, editando o validando herramientas, recursos o prompts en un servidor MCP de NitroStack.

Definiendo herramientas con @Tool

Una herramienta MCP expone una función que un cliente de IA puede invocar. Decora un método de servicio o controlador con @Tool.

Opciones clave de herramienta:

  • name: Identificador único en kebab-case o snake_case.
  • description: Descripción detallada que explica cuándo y cómo el cliente debe usarla.
  • inputSchema: Un esquema de objeto Zod para una validación estricta de entradas.
  • outputSchema (opcional): Esquema Zod que valida la estructura de salida.
import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core';

@Controller('weather')
export class WeatherService {
  @Tool({
    name: 'get_current_weather',
    description: 'Obtén el pronóstico del tiempo actual para una ciudad específica.',
    inputSchema: z.object({
      city: z.string().describe('El nombre de la ciudad, p. ej., San Francisco'),
      unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
    }),
  })
  @InitialTool() // Se invoca automáticamente cuando el cliente de IA se inicializa/inicia
  async getWeather(
    input: { city: string; unit: 'celsius' | 'fahrenheit' },
    ctx: ExecutionContext
  ) {
    ctx.logger.info(`Obteniendo clima para ${input.city}`);
    // implementación
    return {
      city: input.city,
      temp: 22,
      condition: 'Soleado',
    };
  }
}

Definiendo recursos con @Resource

Un recurso MCP expone archivos/URIs de datos estáticos o dinámicos que el cliente de IA puede leer.

Opciones clave de recurso:

  • uri: Patrón URI (p. ej., git://{owner}/{repo}/file o estático app://config).
  • name: Nombre único del recurso.
  • description: Explicación de qué datos proporciona este recurso.
  • mimeType: Tipo MIME de la respuesta (p. ej., text/plain, application/json).
import { Resource, ExecutionContext } from '@nitrostack/core';

export class ConfigResources {
  @Resource({
    uri: 'app://settings',
    name: 'Configuración de la aplicación',
    description: 'Parámetros y configuraciones de todo el sistema.',
    mimeType: 'application/json',
  })
  async getSettings(ctx: ExecutionContext) {
    return {
      environment: 'development',
      debugMode: true,
    };
  }
}

Definiendo prompts con @Prompt

Un prompt MCP expone plantillas reutilizables o conjuntos de instrucciones que guían a los LLM.

Opciones clave de prompt:

  • name: Nombre del prompt.
  • description: Describe qué tarea ayuda a realizar este prompt.
  • arguments: Declara parámetros que el cliente puede proporcionar para personalizar la plantilla del prompt.
import { Prompt, ExecutionContext } from '@nitrostack/core';

export class PromptTemplates {
  @Prompt({
    name: 'code_review',
    description: 'Proporciona una revisión de código exhaustiva para un fragmento de código dado.',
    arguments: [
      { name: 'language', description: 'El lenguaje de programación, p. ej., TypeScript', required: true },
      { name: 'code', description: 'El fragmento de código a revisar', required: true },
    ],
  })
  async getCodeReviewPrompt(
    args: { language: string; code: string },
    ctx: ExecutionContext
  ) {
    return {
      messages: [
        {
          role: 'user',
          content: `Eres un ingeniero de software experto. Revisa este código ${args.language}:\n\n${args.code}`,
        },
      ],
    };
  }
}

Políticas de herramientas: Almacenamiento en caché (@Cache) y limitación de tasa (@RateLimit)

Puedes controlar los comportamientos de ejecución de herramientas (como la optimización de rendimiento y la limitación) usando decoradores de método.

1. Almacenamiento en caché con @Cache

Usa @Cache para almacenar en caché las salidas de ejecución de herramientas durante una duración específica (TTL en segundos). Esto reduce la sobrecarga de base de datos o API para solicitudes idénticas frecuentes.

Opciones:

  • ttl: Tiempo de vida de la caché en segundos (obligatorio).
  • key (opcional): Función personalizada (input: any, context?: any) => string que devuelve una clave de caché única basada en las entradas. Si no se define, se genera automáticamente una clave a partir de los argumentos de entrada serializados.

Ejemplo:

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

export class StationTools {
  @Tool({
    name: 'get_system_status',
    description: 'Obtiene métricas de la estación en tiempo real. La respuesta se almacena en caché.',
    inputSchema: z.object({}),
  })
  @Cache({ ttl: 60 }) // Almacena en caché el estado durante 60 segundos
  async getSystemStatus() {
    return { temperature: 21.5, oxygen: 0.98 };
  }

  @Tool({
    name: 'get_crew_status',
    description: 'Obtiene el estado de un miembro de la tripulación. Almacenado en caché por ID de tripulación.',
    inputSchema: z.object({ id: z.string() }),
  })
  @Cache({
    ttl: 300,
    key: (input) => `crew:status:${input.id}`
  })
  async getCrewStatus(input: { id: string }) {
    // ...
  }
}

2. Limitación de tasa con @RateLimit

Usa @RateLimit para restringir el número de invocaciones de herramientas dentro de una ventana de tiempo específica para evitar abusos del cliente.

Opciones:

  • requests: Número de solicitudes permitidas en la ventana (obligatorio).
  • window: Duración de la ventana de limitación (obligatorio). Soporta formatos como '1s', '1m', '1h'.
  • key (opcional): Función personalizada (context: ExecutionContext) => string para agrupar límites de tasa. Útil para limitar la tasa por rol de usuario o clave de API.

Ejemplo:

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

export class DiagnosticTools {
  @Tool({
    name: 'run_deep_diagnostic',
    description: 'Ejecuta diagnósticos intensivos. Con limitación de tasa.',
    inputSchema: z.object({}),
  })
  @RateLimit({ requests: 3, window: '1m' }) // Máximo 3 solicitudes por minuto a nivel global
  async runDeepDiagnostic() {
    return { diagnosticReport: 'Todos los sistemas operativos.' };
  }

  @Tool({
    name: 'request_supply_drop',
    description: 'Solicita suministros de inventario. Con limitación de tasa por usuario.',
    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) {
    // ...
  }
}

Manejo de cargas de archivos en herramientas

NitroStack admite la carga de archivos desde clientes MCP (como NitroStudio) mediante el paso del archivo como una cadena codificada en base64 dentro de los parámetros de entrada de una herramienta.

1. Declaración del esquema de entrada para cargas de archivos

Para aceptar un archivo cargado, define tres campos Zod en el inputSchema de tu herramienta:

  • file_name: El nombre del archivo (p. ej. report.csv).
  • file_type: El tipo MIME (p. ej. text/csv).
  • file_content: La cadena codificada en base64 que contiene los datos del archivo.
import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';

export class FileTools {
  @Tool({
    name: 'upload_document',
    description: 'Carga un documento de texto o una imagen.',
    inputSchema: z.object({
      file_name: z.string().describe('Nombre del archivo cargado'),
      file_type: z.string().describe('Tipo MIME del archivo cargado'),
      file_content: z.string().describe('Contenido del archivo codificado en base64'),
    })
  })
  async uploadDocument(input: any, ctx: ExecutionContext) {
    // Lógica de procesamiento
  }
}

2. Decodificación de cargas en base64

Las cargas de archivos pueden llegar en dos formatos dependiendo del cliente:

  1. Formato de URL de datos: data:image/png;base64,iVBORw0KGgo...
  2. Formato base64 sin procesar: iVBORw0KGgo...

Usa el siguiente patrón de decodificador universal para analizar cualquiera de los formatos en un Buffer de 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 de URL de datos - decodificar matches[2]
    return Buffer.from(matches[2], 'base64');
  } else {
    // Formato base64 sin procesar - decodificar entrada directamente
    return Buffer.from(content, 'base64');
  }
}

3. Ejemplo de guardado seguro de archivos

Valida siempre las rutas de directorio para prevenir ataques de path traversal al guardar archivos en el 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 y guarda un archivo cargado de forma segura.',
    inputSchema: z.object({
      file_name: z.string().describe('Nombre del archivo cargado'),
      file_type: z.string().describe('Tipo MIME del archivo cargado'),
      file_content: z.string().describe('Contenido del archivo codificado en base64'),
    })
  })
  async saveFile(input: any, ctx: ExecutionContext) {
    // Asegura que el directorio de cargas exista
    if (!fs.existsSync(UPLOAD_DIR)) {
      fs.mkdirSync(UPLOAD_DIR, { recursive: true });
    }

    // Ruta de destino segura 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('Se detectó una ruta de archivo no válida (path traversal).');
    }

    // Decodificar y escribir en disco
    const buffer = decodeBase64File(input.file_content);
    fs.writeFileSync(filePath, buffer);

    ctx.logger.info(`Archivo guardado correctamente: ${safeName}`);
    return { success: true, path: filePath };
  }
}

Individual skills in this repo

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

Skills relacionados