使用タイミング
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('都市名(例: San Francisco)'),
unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
}),
})
@InitialTool() // AI クライアントの初期化/開始時に自動呼び出し
async getWeather(
input: { city: string; unit: 'celsius' | 'fahrenheit' },
ctx: ExecutionContext
) {
ctx.logger.info(`${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: 'Application Settings',
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: キャッシュの生存期間(秒)(必須)。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' }) // グローバルに 1 分あたり最大 3 リクエスト
async runDeepDiagnostic() {
return { diagnosticReport: '全システム稼働中。' };
}
@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 に 3 つの 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 ペイロードのデコード
クライアントに応じて、ファイルアップロードは 2 つの形式で到着します:
- データ URL 形式:
data:image/png;base64,iVBORw0KGgo... - 生の 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 };
}
}