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}/fileoder statischapp://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) => stringzum 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:
- Data-URL-Format:
data:image/png;base64,iVBORw0KGgo... - 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 };
}
}