返回市场
MCPE服务器

MCPE服务器

作者:johnlindquist5 星标更新:2025-10-17

项目介绍

mcpez

一个用于使用TypeScript和Bun构建MCP服务器的最小化且符合人体工程学的ESM包装器。

<img src="./mcpez.png" alt="mcpez" style="max-height: 400px;" />

安装

bun add mcpez

快速开始

最小示例

提示

<!-- 来源: tests/examples/prompt.poem.ts -->
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捆绑在一起,因此无需单独安装它。

为什么捆绑Zod

mcpez捆绑了Zod v3以确保与MCP SDK的兼容性,该SDK需要特定的Zod v3版本。由于Zod v4具有破坏性的更改,会导致运行时错误如keyValidator._parse is not a function,捆绑Zod v3可以防止版本冲突,并提供一个更简单、无错误的开发者体验。你可以直接从mcpez导入z

工具

<!-- 来源: tests/examples/tool.minimal.ts -->
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启动。

资源

<!-- 来源: tests/examples/resource.minimal.ts -->
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),
        },
      ],
    }
  },
)

资源模板

<!-- 来源: tests/examples/resourceTemplate.logs.ts -->
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,
          ),
        },
      ],
    }
  },
)

日志和通知

<!-- 来源: tests/examples/logging.minimal.ts -->
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("服务器尚未启动,日志被排队")
}

完全配置示例

<!-- 来源: tests/examples/full.server.ts -->
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" })

API

  • 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

此包仅提供ESM。确保你的项目有"type": "module"或使用ES模块导入语法。

许可证

MIT