何时使用
在 NitroStack MCP 服务器上定义、编辑或验证工具、资源或提示时,请使用此技能。
使用 @Tool 定义工具
MCP 工具暴露一个可由 AI 客户端调用的函数。使用 @Tool 装饰服务或控制器方法。
关键工具选项:
name:采用 kebab-case 或 snake_case 的唯一标识符。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: '应用程序设置',
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' }) // 全局每分钟最多 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 支持从 MCP 客户端(如 NitroStudio)上传文件,方式是将文件作为 base64 编码的字符串传递到工具的输入参数中。
1. 声明文件上传的输入模式
要接受上传的文件,请在工具的 inputSchema 中定义三个 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 负载
根据客户端的不同,文件上传可能以两种格式到达:
- Data 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) {
// Data 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 };
}
}