CommunityCodierung & Entwicklunggithub.com

joshichinmay016/AgentProof

Richtlinien und Muster für die Definition von Tools, Ressourcen und Prompts in einer NitroStack-Anwendung mit Schema-Validierung durch Zod, inklusive Caching, Ratenbegrenzung und Base64-Dateiuploads.

Was ist AgentProof?

Diese Skill-Anleitung beschreibt, wie Entwickler Tools, Ressourcen und Prompts auf einem NitroStack MCP-Server definieren, bearbeiten und validieren. Sie zeigt die Verwendung der Dekorateure @Tool, @Resource und @Prompt zusammen mit Zod-Schemata zur strengen Eingabe- und Ausgabevalidierung. Darüber hinaus werden erweiterte Funktionen wie Caching mit @Cache und Ratenbegrenzung mit @RateLimit behandelt, um die Leistung zu optimieren und Missbrauch zu verhindern. Die Anleitung enthält außerdem eine detaillierte Erklärung zur Verarbeitung von Dateiuploads über Base64-kodierte Nutzdaten, einschließlich eines universellen Decodierungsmusters und sicherer Dateispeicherung. Die bereitgestellten Codebeispiele sind in TypeScript und nutzen das @nitrostack/core-Paket. Die Skill ist sowohl für die Erstellung neuer MCP-Komponenten als auch für die Überprüfung und Optimierung bestehender Implementierungen hilfreich.

Funktioniert mit~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 Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Wann verwenden

Verwenden Sie diese Skill, wenn Sie Tools, Ressourcen oder Prompts auf einem NitroStack MCP-Server definieren, bearbeiten oder validieren.

Definieren von Tools mit @Tool

Ein MCP-Tool stellt eine Funktion bereit, die ein AI-Client aufrufen kann. Dekorieren Sie eine Dienst- oder Controller-Methode mit @Tool.

Wichtige Tool-Optionen:

  • name: Eindeutiger Bezeichner in Kebab-Case oder Snake-Case.
  • description: Detaillierte Beschreibung, die erklärt, wann und wie der Client es verwenden soll.
  • inputSchema: Ein Zod-Objektschema zur strengen Validierung der Eingaben.
  • outputSchema (optional): Zod-Schema zur Validierung der Ausgabestruktur.
import { ToolDecorator as Tool, ControllerDecorator as Controller, InitialTool, z, ExecutionContext } from '@nitrostack/core';

@Controller('weather')
export class WeatherService {
  @Tool({
    name: 'get_current_weather',
    description: 'Ruft die aktuelle Wettervorhersage für eine bestimmte Stadt ab.',
    inputSchema: z.object({
      city: z.string().describe('Der Name der Stadt, z. B. Berlin'),
      unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
    }),
  })
  @InitialTool() // Wird automatisch aufgerufen, wenn der AI-Client initialisiert/startet
  async getWeather(
    input: { city: string; unit: 'celsius' | 'fahrenheit' },
    ctx: ExecutionContext
  ) {
    ctx.logger.info(`Wetterdaten werden abgerufen für ${input.city}`);
    // Implementierung
    return {
      city: input.city,
      temp: 22,
      condition: 'Sonnig',
    };
  }
}

Definieren von Ressourcen mit @Resource

Eine MCP-Ressource stellt statische oder dynamische Daten/URIs bereit, die der AI-Client lesen kann.

Wichtige Ressourcen-Optionen:

  • uri: URI-Muster (z. B. git://{owner}/{repo}/file oder statisch app://config).
  • name: Eindeutiger Name der Ressource.
  • description: Erklärung, welche Daten diese Ressource bereitstellt.
  • mimeType: MIME-Typ der Antwort (z. B. text/plain, application/json).
import { Resource, ExecutionContext } from '@nitrostack/core';

export class ConfigResources {
  @Resource({
    uri: 'app://settings',
    name: 'Anwendungseinstellungen',
    description: 'Systemweite Konfigurationseinstellungen und Parameter.',
    mimeType: 'application/json',
  })
  async getSettings(ctx: ExecutionContext) {
    return {
      environment: 'development',
      debugMode: true,
    };
  }
}

Definieren von Prompts mit @Prompt

Ein MCP-Prompt stellt wiederverwendbare Vorlagen oder Anweisungssätze bereit, die LLMs leiten.

Wichtige Prompt-Optionen:

  • name: Name des Prompts.
  • description: Beschreibt, welche Aufgabe dieser Prompt zu erledigen hilft.
  • arguments: Deklariert Parameter, die der Client bereitstellen kann, um die Prompt-Vorlage anzupassen.
import { Prompt, ExecutionContext } from '@nitrostack/core';

export class PromptTemplates {
  @Prompt({
    name: 'code_review',
    description: 'Führt eine intensive Code-Review für ein gegebenes Code-Snippet durch.',
    arguments: [
      { name: 'language', description: 'Die Programmiersprache, z. B. TypeScript', required: true },
      { name: 'code', description: 'Das zu überprüfende Code-Snippet', required: true },
    ],
  })
  async getCodeReviewPrompt(
    args: { language: string; code: string },
    ctx: ExecutionContext
  ) {
    return {
      messages: [
        {
          role: 'user',
          content: `Sie sind ein erfahrener Softwareentwickler. Überprüfen Sie diesen ${args.language}-Code:\n\n${args.code}`,
        },
      ],
    };
  }
}

Tool-Richtlinien: Caching (@Cache) und Ratenbegrenzung (@RateLimit)

Sie können das Ausführungsverhalten von Tools (z. B. Leistungsoptimierung und Drosselung) mithilfe von Methodendekorateuren steuern.

1. Caching mit @Cache

Verwenden Sie @Cache, um Tool-Ausgabeergebnisse für eine bestimmte Dauer (TTL in Sekunden) zwischenzuspeichern. Dies reduziert den Datenbank- oder API-Overhead bei häufigen identischen Anfragen.

Optionen:

  • ttl: Cache-Zeit bis zum Ablauf in Sekunden (erforderlich).
  • key (optional): Benutzerdefinierte Funktion (input: any, context?: any) => string, die einen eindeutigen Cache-Schlüssel basierend auf den Eingaben zurückgibt. Wenn nicht definiert, wird automatisch ein Schlüssel aus serialisierten Eingabeargumenten generiert.

Beispiel:

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

export class StationTools {
  @Tool({
    name: 'get_system_status',
    description: 'Ruft Echtzeit-Stationsmetriken ab. Die Antwort wird zwischengespeichert.',
    inputSchema: z.object({}),
  })
  @Cache({ ttl: 60 }) // Cachet den Status für 60 Sekunden
  async getSystemStatus() {
    return { temperature: 21.5, oxygen: 0.98 };
  }

  @Tool({
    name: 'get_crew_status',
    description: 'Ruft den Status eines Besatzungsmitglieds ab. Wird nach Besatzungs-ID gecached.',
    inputSchema: z.object({ id: z.string() }),
  })
  @Cache({
    ttl: 300,
    key: (input) => `crew:status:${input.id}`
  })
  async getCrewStatus(input: { id: string }) {
    // ...
  }
}

2. Ratenbegrenzung mit @RateLimit

Verwenden Sie @RateLimit, um die Anzahl der Tool-Aufrufe innerhalb eines bestimmten Zeitfensters zu begrenzen und so Missbrauch durch Clients zu verhindern.

Optionen:

  • requests: Anzahl der erlaubten Anfragen im Fenster (erforderlich).
  • window: Dauer des Drosselungsfensters (erforderlich). Unterstützt Formate wie '1s', '1m', '1h'.
  • key (optional): Benutzerdefinierte Funktion (context: ExecutionContext) => string zum Gruppieren von Ratenbegrenzungen. Nützlich für die Ratenbegrenzung pro Benutzerrolle oder API-Schlüssel.

Beispiel:

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

export class DiagnosticTools {
  @Tool({
    name: 'run_deep_diagnostic',
    description: 'Führt intensive Diagnosen durch. Ratenbegrenzt.',
    inputSchema: z.object({}),
  })
  @RateLimit({ requests: 3, window: '1m' }) // Maximal 3 Anfragen pro Minute global
  async runDeepDiagnostic() {
    return { diagnosticReport: 'Alle Systeme betriebsbereit.' };
  }

  @Tool({
    name: 'request_supply_drop',
    description: 'Fordert Bestandslieferungen an. Ratenbegrenzt pro Benutzer.',
    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) {
    // ...
  }
}

Verarbeitung von Dateiuploads in Tools

NitroStack unterstützt Dateiuploads von MCP-Clients (wie NitroStudio), indem die Datei als base64-kodierter String innerhalb der Eingabeparameter eines Tools übergeben wird.

1. Deklarieren des Eingabeschemas für Dateiuploads

Um eine hochgeladene Datei zu akzeptieren, definieren Sie drei Zod-Felder im inputSchema Ihres Tools:

  • file_name: Der Name der Datei (z. B. bericht.csv).
  • file_type: Der MIME-Typ (z. B. text/csv).
  • file_content: Der base64-kodierte String mit den Dateidaten.
import { ToolDecorator as Tool, ExecutionContext, z } from '@nitrostack/core';

export class FileTools {
  @Tool({
    name: 'upload_document',
    description: 'Lädt ein Textdokument oder Bild hoch.',
    inputSchema: z.object({
      file_name: z.string().describe('Name der hochgeladenen Datei'),
      file_type: z.string().describe('MIME-Typ der hochgeladenen Datei'),
      file_content: z.string().describe('Base64-kodierter Dateiinhalt'),
    })
  })
  async uploadDocument(input: any, ctx: ExecutionContext) {
    // Verarbeitungslogik
  }
}

2. Dekodieren von Base64-Nutzdaten

Dateiuploads können je nach Client in zwei Formaten eingehen:

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

Verwenden Sie das folgende universelle Dekodierungsmuster, um jedes Format in einen Node Buffer zu parsen:

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) {
    // Data-URL-Format - dekodiere matches[2]
    return Buffer.from(matches[2], 'base64');
  } else {
    // Raw-Base64-Format - dekodiere Eingabe direkt
    return Buffer.from(content, 'base64');
  }
}

3. Sicheres Dateispeicherbeispiel

Validieren Sie immer die Verzeichnispfade, um Verzeichnis-Traversal-Angriffe zu verhindern, wenn Sie Dateien auf die Festplatte speichern.

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: 'Dekodiert und speichert eine hochgeladene Datei sicher.',
    inputSchema: z.object({
      file_name: z.string().describe('Name der hochgeladenen Datei'),
      file_type: z.string().describe('MIME-Typ der hochgeladenen Datei'),
      file_content: z.string().describe('Base64-kodierter Dateiinhalt'),
    })
  })
  async saveFile(input: any, ctx: ExecutionContext) {
    // Stelle sicher, dass das Uploads-Verzeichnis existiert
    if (!fs.existsSync(UPLOAD_DIR)) {
      fs.mkdirSync(UPLOAD_DIR, { recursive: true });
    }

    // Sicherer Zielpfad zur Verhinderung von Pfad-Traversal
    const safeName = path.basename(input.file_name);
    const filePath = path.join(UPLOAD_DIR, safeName);
    if (!filePath.startsWith(UPLOAD_DIR)) {
      throw new Error('Ungültiger Dateipfad erkannt (Pfad-Traversal).');
    }

    // Dekodieren und auf die Festplatte schreiben
    const buffer = decodeBase64File(input.file_content);
    fs.writeFileSync(filePath, buffer);

    ctx.logger.info(`Datei erfolgreich gespeichert: ${safeName}`);
    return { success: true, path: filePath };
  }
}

Individual skills in this repo

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

Verwandte Skills