@empyria / mcp
模型上下文协议 助手的 Empyria,一个
主要构建在Bun上的纳米服务框架:发布一个分子型服务列表
MCP工具,通过JSON模式输入验证 @empyria/common的 createValidator - - - - - -
没有Zod,没有Ajv,在我们自己的代码里。
要求
- 包子
>=1.4.0或node . js>=26 - 简单的ESM,没有构建步骤,没有TypeScript
这两个要求都来自 @empyria /分类
和 @empyria /常见这个包裹
取决于和哪个使用本机 Temporal 全局用于所有日期/时间处理。
安装
bun add @empyria/mcp
使用
import { createMcpServer } from '@empyria/mcp'
import { string, number } from '@empyria/common'
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'
const WeatherService = {
name: 'Weather',
actions: {
getForecast: {
description: 'Get the weather forecast for a city',
params: { city: string(), days: number(3) },
handler: async ({ city, days }) => ({ city, days, forecast: 'sunny' }),
},
// bare functions work too — no params schema, no description
ping: async () => 'pong',
},
}
const server = createMcpServer({
name: 'my-mcp-server',
version: '1.0.0',
services: [WeatherService],
})
await server.connect(new StdioServerTransport())
每个动作都成为一个名为 "<service.name>.<actionName>" - - - - - - Weather.getForecast,
Weather.ping 上面镜像分子自己的 service.action 命名。 params 是JSON
使用构建的架构属性定义 @empyria/common的模式辅助程序(string(),
number(), bool(), enumType(),……);传入的工具调用之前会根据它们进行验证
处理程序运行,验证失败将作为正常情况报告回MCP客户端
工具调用错误(isError: true) -处理程序永远不会看到无效输入。处理程序的返回
value可以是字符串,任何json可序列化的值,或者已经形成mcp的值
{ content: [...] } 结果;抛出的错误也作为工具调用错误报告回来
自动。
createMcpServer 只构建和注册-连接到传输(stdio, HTTP,…)是
由你决定。关于工作室,请参见 @modelcontextprotocol/server/stdio的 StdioServerTransport/
serveStdio. 对于HTTP,这个包有它自己的helper:
import { serveMcpHttp } from '@empyria/mcp'
serveMcpHttp(
{ name: 'my-mcp-server', version: '1.0.0', services: [WeatherService] },
{ port: 3000 },
)
serveMcpHttp 为MCP服务 Bun.serve 直接-不需要web框架。MCP的官方
createMcpHandler 生成一个简单的web标准 { fetch: (Request) => Promise<Response>, close } 处理程序(可用于任何Fetch-API服务器,包括Bun); @modelcontextprotocol/hono
在几个可选适配器中只有一个用于将处理程序连接到Hono,不是吗
一个要求。如果你需要裸 {fetch, close} 处理程序,而不是已经运行的Bun
服务器(例如安装在您自己的 Bun.serve,或另一个基于fetch的运行时),使用
createMcpHttpHandler(options) 代替。
使用令牌保护服务
服务可以通过添加令牌来要求令牌 resolveToken 函数- mcp端等效的
empyria-guard-moleculer的 MetaGuard.middleware.js,重复使用与它相同的形状
getUser(ctx, action):
const SecretsService = {
name: 'Secrets',
async resolveToken(tokenKey, { service, action }) {
// look the token up however you like — call another service, hit a DB, etc.
// return the resolved user/session, or a falsy value to reject the call
return lookupUser(tokenKey)
},
actions: {
reveal: {
params: { id: string() },
handler: async ({ id }, extra) => `secret for ${extra.user.name}`,
},
},
}
令牌从 Authorization 头文件(可选) Bearer 前缀被剥离),
或自定义标题via guard: { tokenHeader: 'x-token-key' } 传递给 createMcpServer/
serveMcpHttp. 缺少令牌或未解析令牌的调用将作为正常的工具调用被拒绝
错误,并将解析的值附加到处理程序的 extra.user是分子的镜像
ctx.meta.user. 这只适用于HTTP -令牌作为请求头传播,工作室有
没有一个;一个 resolveToken通过studio调用的受保护服务总是被拒绝。服务,而不
resolveToken 不受影响(公开)-它完全是选择加入,每个服务。
作为客户机连接
import { connectStdioClient, connectHttpClient } from '@empyria/mcp'
const stdioClient = await connectStdioClient({
name: 'my-client',
version: '1.0.0',
command: 'node',
args: ['./my-server.js'],
})
const httpClient = await connectHttpClient({
name: 'my-client',
version: '1.0.0',
url: 'http://localhost:3000',
token: 'my-token', // sends Authorization: Bearer my-token
})
const { tools } = await httpClient.listTools()
await httpClient.callTool({ name: 'Weather.getForecast', arguments: { city: 'Zurich' } })
将多个MCP服务器聚合为一个(网关)
如果您有多个MCP服务器,并希望将它们作为一个代理主机公开
连接-更少的配置项,一个地方应用 lib/Guard.js等-连接到每个as
一个客户,然后交给他们 createMcpGateway:
import { connectStdioClient, connectHttpClient, createMcpGateway } from '@empyria/mcp'
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'
const weather = await connectStdioClient({
name: 'gw',
version: '1.0.0',
command: 'node',
args: ['./weather-server.js'],
})
const search = await connectHttpClient({
name: 'gw',
version: '1.0.0',
url: 'http://localhost:4000',
})
const gateway = await createMcpGateway({
name: 'my-gateway',
version: '1.0.0',
upstreams: [
{ name: 'Weather', client: weather },
{ name: 'Search', client: search },
],
})
await gateway.connect(new StdioServerTransport())
每个上游的工具都在前面列出一次,并作为
"<upstream.name>.<toolName>";调用被转发到所属的上游及其结果
直接通过,第二次未验证-上游已经验证过了。
建立一个网关确实有用 listTools() 每个上游),因此构建一次并重用
相同的实例——包括as createMcpHandlerHTTP的per-request factory,例如inside
serveMcpHttp的 bunOptions.fetch -而不是每次请求都重新连接上游。
模块
| 模块 | 目的 |
|---|---|
| lib / Server.js | createMcpServer -发布了一个分子型的列表 { name, actions } 服务作为MCP工具。 |
| lib / Http.js | createMcpHttpHandler (运行时无关的web标准处理程序)和 serveMcpHttp (只有面包方便,包装 Bun.serve)。 |
| lib / Guard.js | withMetaGuard -可选的每个服务令牌保护;见上图。 |
| lib / Client.js | createMcpClient, connectStdioClient, connectHttpClient -客户端助手。 |
| lib / Gateway.js | createMcpGateway -将多个已连接的上游MCP客户端聚合到一个服务器上。 |
| lib / Validator.js | empyriaJsonSchemaValidator -适应 @empyria/common的 createValidator 到MCP SDK的可插拔JSON模式验证器接口,相同的扩展点是它自己的 ajv/cf-worker 供应商使用。 |
没有官方的JSON-Schema-only便利API @modelcontextprotocol/server ——它的
registerTool() 仍然需要一个“标准模式”(Zod生成的)。什么 是 官方和
Zod-free是 fromJsonSchema(schema, validatorProvider),它包装了一个普通的JSON Schema对象
转换为给定验证器提供程序的标准模式——就是这样 lib/Server.js 用途,由
lib/Validator.js 而不是SDK自己的 ajv/cf-worker 提供者。
每个导出的函数都直接用JSDoc记录在其源文件中——悬停
VSCode或Zed中的函数显示其参数和返回类型,不需要任何额外的内容
工具,因为两个编辑器都从plain读取JSDoc .js 自动文件。
测试在 测试/,每个模块一个文件。他们开着真正的
@modelcontextprotocol/client Client 反对服务器over InMemoryTransport 而不是
嘲弄协议。
脚本
bun run format # check formatting (oxfmt)
bun run format:fix # apply formatting
bun run lint # lint (oxlint)
bun run lint:fix # lint and fix
bun run test # run tests with coverage
许可证
MIT©Imre Fazekas