用装饰器构建 AI 工具,太爽了
忘掉你所知道的关于构建 MCP 服务器的一切。TypeScript 装饰器将 AI 工具开发从繁琐的配置转变为纯粹、富有表现力的代码。体验 @zhama/mcp-server 装饰器驱动开发的乐趣。

装饰器就是魔法。
不是那种虚假的、花里胡哨的魔法。是真正的魔法 - 复杂的操作变得简单,冗长的代码变得优雅,而开发变得...有趣。
当我们在 Zhama 构建 @zhama/mcp-server 时,我们只有一个目标:让为 Claude Desktop 构建 AI 工具的感觉,不再像是与配置搏斗,而更像是在写诗。
答案?TypeScript 装饰器。
让我展示为什么用装饰器构建 AI 工具是扩展 Claude 能力最令人愉悦的方式。
一切改变的那一刻
我记得那个确切的时刻。凌晨 2 点,我正喝着第五杯咖啡,盯着 200 行 JSON Schema 定义,只是为了一个简单的邮件工具。我的大脑快要融化了。
然后我想:"如果我可以直接...描述我想要的呢?"
@Tool({
name: 'send_email',
description: '发送邮件',
parameters: [
{ name: 'to', type: 'string', required: true },
{ name: 'subject', type: 'string', required: true },
{ name: 'body', type: 'string', required: true }
]
})
class EmailTool extends BaseTool {
async executeInternal(params) {
// 只需发送邮件
return await sendEmail(params);
}
}
**就这样。**没有 schema 对象。没有处理器注册。没有请求路由。只有纯粹的、声明式的美。
我删除了 180 行样板代码,开心地去睡觉了。
是什么让装饰器如此优秀?
1. 它们读起来像自然语言
看看这个:
@Tool({
name: 'get_weather',
description: '获取任何城市的当前天气',
parameters: [
{ name: 'city', type: 'string', description: '城市名称', required: true }
]
})
**你能读懂这个。**你不需要是 TypeScript 专家。你不需要理解 JSON Schema。你就是...理解它。
对比传统方法:
const weatherToolSchema = {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名称'
}
},
required: ['city']
};
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'get_weather',
description: '获取任何城市的当前天气',
inputSchema: weatherToolSchema
}]
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_weather') {
// 终于,你的实际代码
}
});
**你更愿意读哪个?**更愿意写哪个?
2. 它们把相关的东西放在一起
在传统方法中,你的工具定义散落在多个地方:
- Schema 定义:第 50 行
- 处理器注册:第 120 行
- 执行逻辑:第 200 行
- 错误处理:第 250 行
想要改变一个参数?访问四个不同的地方。
使用装饰器,所有东西都在一个地方:
@Tool({
name: 'search_database',
description: '在产品数据库中搜索商品',
parameters: [
{ name: 'query', type: 'string', description: '搜索查询', required: true },
{ name: 'limit', type: 'number', description: '最大结果数', required: false }
]
})
class SearchTool extends BaseTool {
protected toolDefinition = {
name: 'search_database',
description: '搜索产品',
parameters: []
};
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { query, limit = 10 } = params as { query: string; limit?: number };
// 验证
if (query.length < 2) {
throw new Error('查询必须至少 2 个字符');
}
// 执行
const results = await this.database.search(query, limit);
// 响应
return {
query,
count: results.length,
results
};
}
}
**关于这个工具的一切都在这里。**定义、验证、逻辑、响应。不用在文件中寻找。
3. 它们支持组合
装饰器组合得很漂亮。想添加认证?日志?速率限制?堆叠装饰器:
@Tool({
name: 'delete_user',
description: '删除用户账户(仅管理员)',
parameters: [
{ name: 'userId', type: 'string', required: true }
]
})
@RequireAuth('admin')
@RateLimit({ maxCalls: 10, windowMs: 60000 })
@AuditLog()
class DeleteUserTool extends BaseTool {
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
// 你的逻辑 - 认证、速率限制和日志都是自动的
return await this.userService.delete(params.userId);
}
}
**每个装饰器添加一个能力。**简洁、模块化、可组合。
让我感到愉悦的真实示例
让我分享一些我用 @zhama/mcp-server 构建的工具,它们让我在编码时微笑。
示例 1:一行数据库查询
@Tool({
name: 'count_users',
description: '统计数据库中的用户总数',
parameters: []
})
class CountUsersTool extends BaseTool {
protected toolDefinition = { name: 'count_users', description: '统计用户', parameters: [] };
protected async executeInternal(): Promise<unknown> {
return { count: await this.db.users.count() };
}
}
**这就是整个工具。**七行实际代码。在 Claude 中完美工作。
对比传统方法:60+ 行,包含 schema 定义、处理器注册、响应格式化。
示例 2:优雅的 API 包装器
@Tool({
name: 'shorten_url',
description: '使用 bit.ly 创建短链接',
parameters: [
{ name: 'url', type: 'string', description: '要缩短的 URL', required: true }
]
})
class ShortenUrlTool extends BaseTool {
protected toolDefinition = { name: 'shorten_url', description: '缩短 URL', parameters: [] };
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { url } = params as { url: string };
const response = await fetch('https://api-ssl.bitly.com/v4/shorten', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.BITLY_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ long_url: url })
});
const data = await response.json();
return {
original: url,
shortened: data.link,
success: true
};
}
}
**优美。**装饰器描述接口,方法实现逻辑。清晰的关注点分离。
示例 3:强大的验证器
@Tool({
name: 'register_user',
description: '注册新用户账户',
parameters: [
{ name: 'email', type: 'string', description: '用户邮箱', required: true },
{ name: 'username', type: 'string', description: '用户名', required: true },
{ name: 'password', type: 'string', description: '密码', required: true }
]
})
class RegisterUserTool extends BaseTool {
protected toolDefinition = { name: 'register_user', description: '注册用户', parameters: [] };
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { email, username, password } = params as {
email: string;
username: string;
password: string;
};
// 验证(自动错误格式化)
if (!this.isValidEmail(email)) {
throw new Error('邮箱格式无效');
}
if (username.length < 3) {
throw new Error('用户名必须至少 3 个字符');
}
if (password.length < 8) {
throw new Error('密码必须至少 8 个字符');
}
// 注册
const user = await this.userService.create({
email,
username,
passwordHash: await this.hashPassword(password)
});
return {
success: true,
userId: user.id,
message: '用户注册成功'
};
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
private async hashPassword(password: string): Promise<string> {
// 密码哈希逻辑
return await bcrypt.hash(password, 10);
}
}
**一切都在它应该在的地方。**验证逻辑和工具在一起。辅助方法是私有的。简洁、可维护、可测试。
模式库
一旦你开始用装饰器思考,模式就会出现。这是我的个人收藏:
模式 1:可选参数默认值
@Tool({
name: 'list_files',
description: '列出目录中的文件',
parameters: [
{ name: 'path', type: 'string', description: '目录路径', required: true },
{ name: 'limit', type: 'number', description: '返回的最大文件数', required: false }
]
})
class ListFilesTool extends BaseTool {
protected toolDefinition = { name: 'list_files', description: '列出文件', parameters: [] };
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { path, limit = 100 } = params as { path: string; limit?: number };
const files = await fs.readdir(path);
return {
path,
files: files.slice(0, limit),
total: files.length,
limited: files.length > limit
};
}
}
**带默认值的解构。**如此简洁,如此 JavaScript。
模式 2:类型安全的枚举
@Tool({
name: 'convert_temperature',
description: '在单位之间转换温度',
parameters: [
{ name: 'value', type: 'number', description: '温度值', required: true },
{ name: 'from', type: 'string', description: '源单位:celsius、fahrenheit、kelvin', required: true },
{ name: 'to', type: 'string', description: '目标单位:celsius、fahrenheit、kelvin', required: true }
]
})
class ConvertTemperatureTool extends BaseTool {
protected toolDefinition = { name: 'convert_temperature', description: '转换温度', parameters: [] };
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { value, from, to } = params as {
value: number;
from: 'celsius' | 'fahrenheit' | 'kelvin';
to: 'celsius' | 'fahrenheit' | 'kelvin';
};
const conversions = {
celsius: {
fahrenheit: (v: number) => (v * 9/5) + 32,
kelvin: (v: number) => v + 273.15
},
fahrenheit: {
celsius: (v: number) => (v - 32) * 5/9,
kelvin: (v: number) => ((v - 32) * 5/9) + 273.15
},
kelvin: {
celsius: (v: number) => v - 273.15,
fahrenheit: (v: number) => ((v - 273.15) * 9/5) + 32
}
};
if (from === to) {
return { value, unit: to };
}
const convert = conversions[from]?.[to];
if (!convert) {
throw new Error(`无法从 ${from} 转换到 ${to}`);
}
return {
original: { value, unit: from },
converted: { value: convert(value), unit: to }
};
}
}
**类型安全 + 查找表。**优雅且安全。
模式 3:异步链
@Tool({
name: 'process_image',
description: '下载、调整大小并上传图片',
parameters: [
{ name: 'url', type: 'string', description: '图片 URL', required: true },
{ name: 'width', type: 'number', description: '目标宽度', required: true },
{ name: 'height', type: 'number', description: '目标高度', required: true }
]
})
class ProcessImageTool extends BaseTool {
protected toolDefinition = { name: 'process_image', description: '处理图片', parameters: [] };
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { url, width, height } = params as {
url: string;
width: number;
height: number;
};
// 优雅的异步管道
const originalBuffer = await this.downloadImage(url);
const resizedBuffer = await this.resizeImage(originalBuffer, width, height);
const uploadedUrl = await this.uploadImage(resizedBuffer);
return {
original: url,
processed: uploadedUrl,
dimensions: { width, height },
success: true
};
}
private async downloadImage(url: string): Promise<Buffer> {
const response = await fetch(url);
return Buffer.from(await response.arrayBuffer());
}
private async resizeImage(buffer: Buffer, width: number, height: number): Promise<Buffer> {
// 图片处理逻辑
return buffer; // 简化版
}
private async uploadImage(buffer: Buffer): Promise<string> {
// 上传逻辑
return 'https://cdn.example.com/processed.jpg'; // 简化版
}
}
**Async/await 链从上到下阅读。**没有回调地狱,没有 promise 链,只有清晰的流程。
模式 4:错误上下文
@Tool({
name: 'charge_payment',
description: '处理支付收费',
parameters: [
{ name: 'amount', type: 'number', description: '金额(分)', required: true },
{ name: 'currency', type: 'string', description: '货币代码', required: true },
{ name: 'customerId', type: 'string', description: '客户 ID', required: true }
]
})
class ChargePaymentTool extends BaseTool {
protected toolDefinition = { name: 'charge_payment', description: '收费', parameters: [] };
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { amount, currency, customerId } = params as {
amount: number;
currency: string;
customerId: string;
};
try {
// 尝试收费
const charge = await this.stripe.charges.create({
amount,
currency,
customer: customerId
});
return {
success: true,
chargeId: charge.id,
amount,
currency
};
} catch (error) {
// 丰富的错误上下文
this.logger.error('支付失败', {
amount,
currency,
customerId,
error: error.message
});
throw new Error(
`支付失败:${error.message}。` +
`客户:${customerId},金额:${amount/100} ${currency.toUpperCase()}`
);
}
}
}
**带上下文的错误处理。**当出错时,你确切地知道发生了什么。
高级装饰器技术
一旦你熟悉了基础知识,试试这些高级模式:
自定义装饰器
为常见模式创建自己的装饰器:
// 用于缓存的自定义装饰器
function Cached(ttlSeconds: number) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const cache = new Map<string, { data: any; expires: number }>();
descriptor.value = async function (...args: any[]) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
const result = await originalMethod.apply(this, args);
cache.set(key, {
data: result,
expires: Date.now() + (ttlSeconds * 1000)
});
return result;
};
return descriptor;
};
}
// 使用它
@Tool({
name: 'get_exchange_rate',
description: '获取货币汇率',
parameters: [
{ name: 'from', type: 'string', required: true },
{ name: 'to', type: 'string', required: true }
]
})
class ExchangeRateTool extends BaseTool {
protected toolDefinition = { name: 'get_exchange_rate', description: '获取汇率', parameters: [] };
@Cached(300) // 缓存 5 分钟
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { from, to } = params as { from: string; to: string };
const response = await fetch(`https://api.exchangerate.com/${from}/${to}`);
const data = await response.json();
return { from, to, rate: data.rate, timestamp: Date.now() };
}
}
装饰器组合
堆叠装饰器以获得强大的组合:
@Tool({
name: 'admin_action',
description: '执行管理操作',
parameters: [
{ name: 'action', type: 'string', required: true },
{ name: 'target', type: 'string', required: true }
]
})
@RequireAuth('admin') // 必须是管理员
@RateLimit(5, 60000) // 每分钟最多 5 次
@AuditLog() // 记录所有操作
@Retry(3, 1000) // 重试 3 次,延迟 1 秒
class AdminActionTool extends BaseTool {
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
// 所有装饰器魔法自动发生
return await this.performAction(params);
}
}
为什么这在规模化时很重要
在 Zhama,我们在多个 Claude 集成中维护 50+ 个 AI 工具。装饰器不仅仅是好 - 它们是必不可少的。
代码审查速度
装饰器之前:
开发者:"能审查一下我的新工具吗?"
审查者:*滚动 150 行*
审查者:"参数验证在哪里?"
开发者:"第 87 行"
审查者:"错误处理在哪里?"
开发者:"120-145 行"
审查者:"实际逻辑在哪里?"
开发者:"60-75 行"
使用装饰器:
开发者:"能审查一下我的新工具吗?"
审查者:*读 40 行*
审查者:"看起来不错,批准"
代码审查时间:20 分钟 → 5 分钟
入职速度
新开发者的第一天:
之前:
- 第 1-2 小时:理解服务器设置
- 第 3-4 小时:学习请求处理器模式
- 第 5-6 小时:研究 schema 定义
- 第 7-8 小时:构建第一个工具
- 总计:8 小时
之后:
- 第 1 小时:"这是一个装饰器示例"
- 第 2 小时:构建第一个工具
- 总计:2 小时
Bug 密度
**装饰器之前:**每 1000 行代码 15 个 bug
- 主要在样板代码中:错误的 schema、缺少验证、响应格式化
**使用装饰器:**每 1000 行代码 3 个 bug
- 全部在业务逻辑中,易于修复
**Bug 减少 5 倍。**不是因为我们是更好的程序员 - 而是因为需要搞砸的代码更少了。
愉悦因子
让我诚实地说:编程应该让人感觉良好。
不是每一天都会完美,但当你在构建东西时,工具应该帮助,而不是阻碍。
装饰器让我开心,因为:
- 我少想机制 - 没有 schema、处理器、格式化的心理负担
- 我看到全局 - 关于工具的一切一眼可见
- 我交付更快 - 5 分钟而不是 5 小时
- 我犯更少错误 - 更少的代码 = 更少的 bug
- 我享受过程 - 写代码感觉像在打磨,而不是在配置
**那种"好得难以置信"的感觉?**是真的。@zhama/mcp-server 兑现了它。
开始装饰器驱动开发
准备好亲自体验了吗?
安装
npm install @zhama/mcp-server
你的第一个装饰器工具
import { createMCPServer, BaseTool, Tool } from '@zhama/mcp-server';
@Tool({
name: 'hello',
description: '向某人问好',
parameters: [
{ name: 'name', type: 'string', description: '要问候的名字', required: true }
]
})
class HelloTool extends BaseTool {
protected toolDefinition = {
name: 'hello',
description: '打招呼',
parameters: []
};
protected async executeInternal(params: Record<string, unknown>): Promise<unknown> {
const { name } = params as { name: string };
return {
greeting: `你好,${name}!👋`,
timestamp: new Date().toISOString()
};
}
}
// 创建并运行
createMCPServer('hello-server', '1.0.0')
.enableTools()
.addTool(new HelloTool())
.runStdio();
保存这个。构建它。看它工作。
当你看到 Claude 使用你的装饰器定义的工具时,你会明白的。
社区正在增长
使用 @zhama/mcp-server 的开发者已经构建了:
- 数据库工具 - 用装饰器查询、插入、更新
- API 集成 - 优雅地包装任何 REST API
- 文件处理器 - 上传、下载、转换文件
- 通知系统 - 邮件、短信、Slack、Discord
- 分析工具 - 查询和可视化数据
- 自动化工作流 - 链接多个操作
每一个都使用装饰器。每一个开发者都喜欢它。
接下来是什么?
我们正在添加更多装饰器功能:
- @Validate() - 参数验证规则
- @Transform() - 自动数据转换
- @Cache() - 响应缓存
- @Retry() - 自动重试逻辑
- @RateLimit() - 请求节流
- @Auth() - 认证要求
装饰器模式可以无限扩展。
结论:愉悦是一个功能
当我们构建 @zhama/mcp-server 时,我们不仅想让开发更快。我们想让它令人愉悦。
装饰器实现了这一点。它们转变了:
- 配置 → 声明
- 样板代码 → 清晰
- 复杂性 → 简单性
- 工作 → 手艺
**结果?**开发者在编写 AI 工具时微笑。
这不是夸张。这是我们在 Zhama 的体验和社区的反馈。
亲自试试:
npm install @zhama/mcp-server
五分钟后,你也会微笑。
由 Zhama 用 ❤️ 和装饰器打造
访问:
- GitHub:github.com/zhama-ai/mcp-server
- 官网:www.zhama.com
分享你的装饰器驱动工具!标记我们或在 GitHub 上开启讨论 - 我们很想看到你构建的东西!
相关文章

为什么现有的 MCP SDK 不够用?开发者的视角
官方 MCP SDK 很强大,但它不是为快速开发设计的。让我们探讨它的局限性,以及为什么像 @zhama/mcp-server 这样的框架对于高效的 AI 工具开发至关重要。

我做了一个框架,让你 5 分钟写出一个 Claude 工具
厌倦了复杂的 MCP 服务器配置?我创建了 @zhama/mcp-server - 一个对开发者友好的 TypeScript 框架,让你从零到拥有一个可用的 Claude Desktop 工具只需 5 分钟。没有样板代码,只有真正的代码。

如何为 Claude Desktop 构建自定义 MCP 服务器:完整实战指南
掌握使用 @zhama/mcp-server 框架为 Claude Desktop 构建自定义模型上下文协议服务器的技能。这份深度指南涵盖了从安装设置到生产部署的全部内容,包含实用示例和生产级最佳实践。