一个用于使用TypeScript和Bun构建MCP服务器的最小化且符合人体工程学的ESM包装器。
<img src="./mcpez.png" alt="mcpez" style="max-height: 400px;" />bun add mcpez
import { prompt, z } from "mcpez"
prompt(
"review-code",
{
description: "审查代码的最佳实践和潜在问题",
argsSchema: {
code: z.string(),
},
},
({ code }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `请审阅此代码:\n\n${code}`,
},
},
],
}),
)
注意: Zod已与mcpez捆绑在一起,因此无需单独安装它。
mcpez捆绑了Zod v3以确保与MCP SDK的兼容性,该SDK需要特定的Zod v3版本。由于Zod v4具有破坏性的更改,会导致运行时错误如keyValidator._parse is not a function,捆绑Zod v3可以防止版本冲突,并提供一个更简单、无错误的开发者体验。你可以直接从mcpez导入z:
import { tool, z } from "mcpez"
tool(
"add",
{
description: "添加两个数字",
inputSchema: {
a: z.number(),
b: z.number(),
},
},
async ({ a, b }) => {
const result = a + b
return {
content: [{ type: "text", text: `${a} + ${b} = ${result}` }],
}
},
)
// 不需要手动调用startServer(); 服务器将在下一个tick启动。
import { resource } from "mcpez"
type EnvironmentConfig = {
settings: {
databaseUrl: string
featureFlags: Record<string, boolean>
}
secrets: {
apiKey: string
}
}
// 按部署环境键入内存配置数据。
const environmentConfigs = new Map<string, EnvironmentConfig>([
[
"production",
{
settings: {
databaseUrl: "postgresql://prod.db.internal/app",
featureFlags: {
betaDashboard: false,
useV2Search: true,
},
},
secrets: {
apiKey: "prod-12345",
},
},
],
[
"staging",
{
settings: {
databaseUrl: "postgresql://staging.db.internal/app",
featureFlags: {
betaDashboard: true,
useV2Search: true,
},
},
secrets: {
apiKey: "staging-67890",
},
},
],
[
"development",
{
settings: {
databaseUrl: "postgresql://localhost:5432/app",
featureFlags: {
betaDashboard: true,
useV2Search: false,
},
},
secrets: {
apiKey: "dev-abcde",
},
},
],
])
resource(
"environment-config",
"config://environment",
{
description: "特定于环境的配置值,带有可选的秘密。",
mimeType: "application/json",
},
async (uri) => {
const params = uri.searchParams
const environment = params.get("env") ?? "production"
const config = environmentConfigs.get(environment)
if (!config) {
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
error: `未知环境: ${environment}`,
availableEnvironments: Array.from(environmentConfigs.keys()),
},
null,
2,
),
},
],
}
}
const includeSecrets = params.get("secrets") === "true"
const payload = {
environment,
settings: config.settings,
...(includeSecrets ? { secrets: config.secrets } : {}),
generatedAt: new Date().toISOString(),
}
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(payload, null, 2),
},
],
}
},
)
import { resourceTemplate } from "mcpez"
type LogLevel = "info" | "warning" | "error"
type LogEntry = {
timestamp: string
message: string
context?: Record<string, unknown>
}
// 按ISO日期和严重级别键入的结构化审计日志。
const auditLogStore: Record<string, Record<LogLevel, LogEntry[]>> = {
"2024-04-01": {
info: [
{ timestamp: "2024-04-01T08:00:00Z", message: "触发部署管道" },
{ timestamp: "2024-04-01T08:05:12Z", message: "成功完成部署" },
],
warning: [
{
timestamp: "2024-04-01T09:12:33Z",
message: "重试连接到Redis领导者",
context: { attempts: 2 },
},
],
error: [
{
timestamp: "2024-04-01T09:15:00Z",
message: "支付网关超时",
context: { orderId: "ORD-481516" },
},
],
},
"2024-04-02": {
info: [
{ timestamp: "2024-04-02T07:45:00Z", message: "后台同步完成" },
{ timestamp: "2024-04-02T10:30:00Z", message: "启用新功能标志" },
],
warning: [
{
timestamp: "2024-04-02T11:05:48Z",
message: "检测到慢数据库查询",
context: { durationMs: 830, query: "SELECT * FROM invoices" },
},
],
error: [],
},
}
const strictLevels: LogLevel[] = ["info", "warning", "error"]
const firstValue = (value: string | string[] | undefined): string | undefined =>
Array.isArray(value) ? value[0] : value
resourceTemplate(
"audit-log",
{
name: "audit-log",
title: "按日期和级别检索审计日志",
uriTemplate: "audit-log://{date}/{level}",
description: "通过ISO日期和严重级别过滤存储的审计日志条目。",
},
{
description: "按日期和严重级别分组的审计日志条目。",
mimeType: "application/json",
},
async (uri, variables) => {
const typedVariables = variables as Record<string, string | string[] | undefined>
const date = firstValue(typedVariables.date)
const level = firstValue(typedVariables.level)
if (!date || !level) {
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
error: "URI模板中必须同时提供{date}和{level}。",
expectedUri: "audit-log://2024-04-01/error",
},
null,
2,
),
},
],
}
}
const normalizedLevel = level.toLowerCase()
if (!strictLevels.includes(normalizedLevel as LogLevel)) {
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
error: `不支持的日志级别: ${level}`,
supportedLevels: strictLevels,
},
null,
2,
),
},
],
}
}
const entriesByLevel = auditLogStore[date]
if (!entriesByLevel) {
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
error: `未找到${date}的日志.`,
availableDates: Object.keys(auditLogStore),
},
null,
2,
),
},
],
}
}
const entries = entriesByLevel[normalizedLevel as LogLevel]
if (!entries || entries.length === 0) {
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
message: `未找到${date}的${normalizedLevel}日志.`,
availableLevels: strictLevels.filter((item) => entriesByLevel[item].length > 0),
},
null,
2,
),
},
],
}
}
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
date,
level: normalizedLevel,
count: entries.length,
entries,
},
null,
2,
),
},
],
}
},
)
import { getServer, log, notifyToolListChanged, tool } from "mcpez"
// 注册一个简单的工具
tool("greet", { description: "问候用户" }, async () => {
// 向客户端发送一条日志消息
log.info("被调用了问候工具")
return {
content: [
{
type: "text",
text: "来自mcpez的问候!",
},
],
}
})
// 注册另一个修改工具列表的工具
tool("add_tool", { description: "模拟添加新的工具" }, async () => {
log.info("这里会添加一个新的工具")
// 通知客户端工具列表已更改
notifyToolListChanged()
return {
content: [
{
type: "text",
text: "工具列表已更改!",
},
],
}
})
// 使用getServer()进行高级操作的例子
const server = getServer()
if (server) {
log.debug("服务器正在运行,可以访问高级API")
} else {
log.debug("服务器尚未启动,日志被排队")
}
import { prompt, resource, startServer, tool, z } from "mcpez"
tool(
"echo",
{
description: "回显提供的消息",
inputSchema: { message: z.string() },
},
async ({ message }) => {
const output = { echo: `工具回显: ${message}` }
return {
content: [{ type: "text", text: JSON.stringify(output) }],
}
},
)
resource(
"echo",
"echo://message",
{
description: "作为资源回显消息",
},
async (uri) => ({
contents: [
{
uri: uri.href,
text: `资源回显: 你好!`,
},
],
}),
)
prompt(
"echo",
{
description: "创建一个处理消息的提示",
argsSchema: { message: z.string() },
},
({ message }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `请处理此消息: ${message}`,
},
},
],
}),
)
// 使用自定义服务器名称和版本启动
await startServer("example-full-server", { version: "1.0.0" })
prompt(name, options, handler)tool(name, options, handler)resource(name, options)resourceTemplate(name, options)startServer(name, serverOptions?, transport?)getServer() - 获取正在运行的服务器实例log.info(data, logger?) - 发送一条日志消息(其他辅助函数:debug, notice, warning, error, critical, alert, emergency)notifyResourceListChanged() - 当资源改变时通知notifyToolListChanged() - 当工具改变时通知notifyPromptListChanged() - 当提示改变时通知所有register*调用都可以在startServer之前进行;它们会被排队并在服务器启动时应用。startServer是可选的,默认为StdioServerTransport。
此包仅提供ESM。确保你的项目有"type": "module"或使用ES模块导入语法。
MIT