内容
# orpc-mcp
将 [oRPC](https://orpc.dev) 路由器作为 [MCP](https://modelcontextprotocol.io) 服务器提供。您已经通过 RPC 和 OpenAPI 提供服务的相同过程现在成为 MCP **工具**、**资源** 和 **提示** — 可供像 Claude、ChatGPT 和 IDEs 这样的客户端使用,具有相同的类型、验证和中间件。
> [!NOTE]
> 这是一个**社区包**,不是 oRPC 核心的一部分。它在 [middleapi/orpc#1604](https://github.com/middleapi/orpc/pull/1604) 中被提出;维护者选择暂时将 MCP 排除在核心之外,并作为生态系统包进行推广。它针对 MCP 协议修订版 `2025-11-25` 和 oRPC `v2`(目前处于测试阶段)。
## 安装
```sh
npm install orpc-mcp
# pnpm add orpc-mcp / yarn add orpc-mcp / bun add orpc-mcp
```
oRPC 包是同级依赖 — 在 `2.0.0-beta.16` 或更高版本安装:
```sh
npm install @orpc/server@beta @orpc/client@beta @orpc/contract@beta @orpc/json-schema@beta @orpc/shared@beta
```
您还需要为验证库安装 JSON 模式转换器,例如 `@orpc/zod@beta` 或 `@orpc/valibot@beta`。
## 设置
将过程公开给 MCP 是**可选的**:使用 `mcp.tool`、`mcp.resource` 或 `mcp.prompt` 进行注解。MCP 元数据独立于任何 `openapi` 元数据,因此单个过程可以同时通过 REST 和 MCP 提供服务。
```ts
import { os } from '@orpc/server'
import { mcp } from 'orpc-mcp'
import * as z from 'zod'
export const createPlanet = os
.meta(mcp.tool({ description: '创建新行星' }))
.input(z.object({ name: z.string() }))
.output(z.object({ id: z.string(), name: z.string() }))
.handler(({ input }) => ({ id: crypto.randomUUID(), name: input.name }))
export const router = { createPlanet }
```
然后使用 `MCPHandler` 适配器之一提供路由器服务。
## 工具
工具是模型可以调用的函数。过程的 `.input()` 成为工具的 JSON 模式,其返回值成为结果,其 `.output()` 添加输出模式和结构化内容。抛出的[类型错误](https://orpc.dev/docs/error-handling)作为工具错误报告回模型,以便其做出反应。
```ts
export const createPlanet = os
.meta(mcp.tool({
description: '创建新行星',
annotations: { destructiveHint: false },
}))
.input(CreatingPlanetSchema)
.output(PlanetSchema)
.handler(({ input }) => create(input))
```
行为提示 — `readOnlyHint`、`destructiveHint`、`idempotentHint`、`openWorldHint` — 放在 `annotations` 中。
## 资源
资源暴露通过 URI 地址的只读数据。使用固定 `uri` 表示单个资源,或使用 `uriTemplate`,其变量映射到过程的输入。
```ts
// 静态资源
export const appConfig = os
.meta(mcp.resource({ uri: 'config://app', mimeType: 'application/json' }))
.output(ConfigSchema)
.handler(() => getConfig())
// 模板资源 — `{id}` 从输入中读取
export const planet = os
.meta(mcp.resource({ uriTemplate: 'planet://{id}', mimeType: 'application/json' }))
.input(z.object({ id: z.string() }))
.output(PlanetSchema)
.handler(({ input }) => findPlanet(input.id))
```
> [!TIP]
> 只有注解只读、无副作用的过程为资源。
## 提示
提示是用户可以调用的可重用模板。参数从过程的 `.input()` 派生,处理程序返回提示消息。
```ts
export const planTrip = os
.meta(mcp.prompt({ description: '计划假期' }))
.input(z.object({ destination: z.string() }))
.output(z.object({
messages: z.array(z.object({
role: z.enum(['user', 'assistant']),
content: z.object({ type: z.literal('text'), text: z.string() }),
})),
}))
.handler(({ input }) => ({
messages: [{ role: 'user', content: { type: 'text', text: `计划前往 ${input.destination} 的行程` } }],
}))
```
## 服务
`MCPHandler` 通过 [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) 传输(Fetch 或 Node.js)或 stdio 提供 MCP 服务。传递验证库的模式转换器 — 与 `@orpc/openapi` 使用的相同转换器。
它建立在 oRPC 的标准请求/响应流程之上,因此工具、资源和提示调用通过您的 [中间件](https://orpc.dev/docs/middleware)、验证和上下文运行,并且任何处理程序插件(CORS、请求限制、OpenTelemetry)按通常方式组合。
### Fetch
```ts
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import { MCPHandler } from 'orpc-mcp/fetch'
const handler = new MCPHandler(router, {
serverInfo: { name: 'planets', version: '1.0.0' },
converters: [new ZodToJsonSchemaConverter()],
})
export async function POST(request: Request) {
const { response } = await handler.handle(request, { context: {} })
return response ?? new Response('未找到', { status: 404 })
}
```
### Node.js
```ts
import { createServer } from 'node:http'
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import { MCPHandler } from 'orpc-mcp/node'
const handler = new MCPHandler(router, { converters: [new ZodToJsonSchemaConverter()] })
createServer((req, res) => handler.handle(req, res, { context: {} })).listen(3000)
```
### stdio
适用于将您的服务器作为子进程启动的客户端(Claude Desktop、IDE 等):
```ts
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import { MCPHandler } from 'orpc-mcp/stdio'
await new MCPHandler(router, { converters: [new ZodToJsonSchemaConverter()] })
.listen({ context: {} })
```
## 授权
身份验证和授权是您应用程序的责任 — 此包对令牌、范围和 OAuth 保持中立。在调用处理程序时提供从请求派生的值作为 `context`,然后使用普通的 [中间件](https://orpc.dev/docs/middleware)强制执行它们,中间件对每个工具、资源和提示调用都运行。
```ts
export const authed = os.use(({ context, next, errors }) => {
const user = verifyToken(context.authToken)
if (!user)
throw errors.UNAUTHORIZED()
return next({ context: { user } })
})
export const deletePlanet = authed
.meta(mcp.tool({ description: '删除行星' }))
.handler(({ context }) => remove(context.user))
```
抛出的 `UNAUTHORIZED` 作为工具错误返回给模型,或作为协议错误返回资源/提示请求。
## 安全
对于浏览器可访问的 HTTP 服务器,启用源和主机验证以防止 DNS 重新绑定攻击。缺少 `Origin` 头部仍然通过,因此非浏览器客户端不受影响。
```ts
export const handler = new MCPHandler(router, {
converters: [new ZodToJsonSchemaConverter()],
enableDnsRebindingProtection: true,
allowedOrigins: ['https://your-app.example'],
allowedHosts: ['your-app.example'],
})
```
## 一个路由器,多个界面
由于 MCP 暴露存在于过程元数据中,因此单个路由器可以同时挂载到多个处理程序上 — RPC、OpenAPI 和 MCP — 在同一实例上:
```ts
export const handlers = {
rpc: new RPCHandler(router), // 类型化的 oRPC 客户端
openapi: new OpenAPIHandler(router), // REST + OpenAPI
mcp: new MCPHandler(router, { converters: [new ZodToJsonSchemaConverter()] }), // MCP 工具 / 资源 / 提示
}
```
## 限制
- 目标 MCP 修订版 `2025-11-25`;在协商期间接受较旧的修订版。
- 每个请求一个 JSON-RPC 消息 — 不支持批处理。
- 服务器发起的流(`GET` SSE 通道)、`listChanged`/`subscribe` 通知和会话未实现。这些将在下一个 MCP 修订版中被删除或替换,因此无状态的请求/响应设计是有意的。
## 开发
```sh
pnpm install
pnpm test # vitest,包括由官方 @modelcontextprotocol/sdk 客户端驱动的 e2e 套件
pnpm lint
pnpm type:check
pnpm build
```
## 许可证
MIT