内容
ception – 工具库
[](https://www.npmjs.comception)
[](LICENSE)
## 目录
- [何时使用 Toolception 以及原因](#何时使用-toolception-以及原因)
- [快速入门指南](#快速入门指南)
- [静态启动](#静态启动)
- [基于权限的快速入门指南](#基于权限的快速入门指南)
- [权限配置方法](#权限配置方法)
- [自定义 HTTP 端点](#自定义-http-端点)
- [每个会话的上下文](#每个会话的上下文)
- [API](#api)
- [createMcpServer](#createmcpserveroptions)
- [createPermissionBasedMcpServer](#createpermissionbasedmcpserveroptions)
- [基于权限的客户端集成](#基于权限的客户端集成)
- [基于权限的安全最佳实践](#基于权限的安全最佳实践)
- [基于权限的常见模式](#基于权限的常见模式)
- [客户端 ID 生命周期](#客户端-id-生命周期)
- [会话 ID 生命周期](#会话-id-生命周期)
- [工具类型](#工具类型)
- [启动模式](#启动模式)
- [许可证](#许可证)
## 何时使用 Toolception 以及原因
构建具有数十或数百种工具的 MCP 服务器通常会损害 LLM 性能和开发人员体验:
- **工具过多会让人不知所措**:较大的工具列表会增加混淆和错误选择的比率。
- **令牌和模式膨胀**:长工具目录会增加提示和延迟。
- **名称冲突和歧义**:不同领域中类似的工具名称会导致失败和脆弱的集成。
- **操作开销**:预先加载所有工具会浪费资源;许多工具是任务特定的。
Toolception 通过将工具分组为工具集并允许您在需要时公开所需的内容来解决这个问题。
### 何时使用 Toolception
- **大型或多领域目录**:您拥有 >20–50 种工具或多个领域(例如搜索、数据、计费),并且不想一次公开所有工具。
- **任务特定工作流**:您希望客户端/代理仅启用与当前任务相关的工具。
- **多租户或策略需求**:不同用户/租户需要不同的工具访问或限制。
- **每个会话的上下文**:您需要为传递给模块加载器的每个客户端会话提供不同的上下文值(API 令牌、用户 ID)。
- **基于权限的访问控制**:您需要为安全、合规或多租户隔离强制执行客户端特定的工具集权限。每个客户端应该只看到和使用他们有权使用的工具集,并通过服务器端或基于标头的权限强制执行。
-冲突命名**:您需要可预测的、命名空间的工具名称以避免- **延迟加载**:某些工具很大,应该按需加载。
### 为什么 Toolception 有帮助
- **工具集**:将相关工具分组并为每个任务公开最小的、一致的子集。
- **动态模式(运行时控制)**:
- 通过元工具(`enable_toolset`、`disable_toolset`、`list_toolsets`、`describe_toolset`、`list_tools`)按需启用工具集。
- 减少提示/工具表面积 → 更好的工具选择和更低的延迟。
- 仅在需要时延迟加载模块生成的工具;安全地将共享的 `context` 传递给加载器。
- 支持 `tools.listChanged` 通知,以便客户端可以对更新的工具列表做出反应。
- **静态模式(可预测的启动)**:
- 在启动时预加载已知的工具集(或全部)以适用于固定的流水线和更简单的环境。
- 仅保留部署足迹所需的集合。
- **暴露策略**:
- **maxActiveToolsets**:活动的集合以防止膨胀。
- **allowlist/denylist**:强制执行可以启用的工具集。
- **namespaceToolsWithSetKey**:默认开启;将工具注册为 `set.tool` 以避免冲突并阐明意图。
- **操作安全性**:
- 中央 `ToolRegistry` 验证名称并防止冲突。
- `ModuleLoaders` 是确定性/幂等的,以实现可重复的运行和缓存。
### 选择模式
- **首选动态模式**,当工具需求因任务而异时,您希望更严格的提示,或者需要运行时门控和延迟加载。
- **选择静态模式**,当您的工具需求稳定且较小,或者您的客户端无法(或不应)执行运行时启用/禁用操作时。
### 典型流程
- **先发现(动态)**:客户端调用 `list_toolsets` → 启用一个集合 → 调用命名空间工具(例如 `core.ping`)。
- **固定管道(静态)**:服务器在启动时预加载命名工具集(或全部);客户端调用 `list_tools` 并像往常一样调用。
## 快速入门指南
### 步骤 1:安装
```bash
npm i toolception
```
### 步骤 2:导入 Toolception
```ts
import { createMcpServer } from "toolception";
```
### 步骤 3:定义工具集目录
```ts
const catalog = {
quotes: { name: "Quotes", description: "市场报价", modules: ["quotes"] },
};
```
### 步骤 4:定义工具
```ts
const quoteTool = {
name: "price",
description: "返回一个假价格",
inputSchema: {
type: "object",
properties: { symbol: { type: "string" } },
required: ["symbol"],
},
handler: async ({ symbol }: { symbol: string }) => ({
content type: "text", text: `${symbol}: 123.45` }],
}),
} as const;
```
### 步骤 5:提供模块加载器
```ts
const moduleLoaders = {
quotes: async () => [quoteTool],
};
```
### 步骤 6:(可选)配置模式
```ts
const configSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
REQUIRED_PARAM: { type: "string", title: "必填参数" },
OPTIONAL_PARAM: { typestring", title: "可选参数" },
required: ["REQUIRED_PARAM"],
} as const;
```
### 步骤 7:创建 MCP SDK 服务器并启动 Toolception
```ts
import {Server } from "@modelcontextprotocol/sdk/server/mcp.js";
// 您拥有 SDK将工厂传递给 Toolception(在动态模式下需要)
const createServer = () =>
new McpServer({
name: "my-mcp-server",
version: "0.0.0",
capabilities: { tools: { listChanged: true } },
});
const { start, close } = await createMcpServer({
moduleLoaders,
startup: { mode: "DYNAMIC" http: { port3000 },
createServer,
// configSchema, // 取消注释以在 /.well-known/mcp-config 处公开
});
await start();
```
### 步骤 8:优雅关闭
```ts
process.on("SIGINT", async () => {
await close();
process.exit(0);
});
process.on("SIGTERM", async () => {
await close();
process.exit(0);
});
```
## 静态启动
在引导时启用部分或全部工具集。在静态模式下,为所有客户端创建一个服务器实例并重用:
```ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const staticCatalog = {
search: { name: "Search", description: "搜索工具", modules: ["search"] },
quotes: { name: "Quotes", description: "市场报价", modules: ["quotes"] },
};
// 加载特定工具集
createMcpServer({
catalog: staticCatalog,
startup: { mode: "STATIC", toolsets: ["search", "quotes"] },
http: { port: 3001 },
createServer: () =>
new McpServer({
name: "static-1",
version: "0.0.0",
capabilities: { tools: { listChanged: false } },
}),
});
// 加载所有工具集
createMcpServer({
catalog: staticCatalog,
startup: { mode: "STATIC", toolsets: "ALL" },
http: { port: 3002 },
createServer: () =>
new McpServer({
name: "static-2",
version: "0.0.0",
capabilities: { tools: { listChanged: false } },
}),
});
```
## 基于权限的快速入门指南
使用 `createPermissionBasedMcpServer` 来强制执行基于客户端的工具集权限。这适用于多租户应用程序、安全敏感环境或不同客户端应具有不同访问级别的情况。
### 步骤 1:安装
```bash
npm i toolception
```
### 步骤 2:导入 Toolception
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
```
### 步骤 3:定义工具集目录
```ts
const catalog = {
admin: {
name: "Admin Tools",
description: "管理操作",
modules: ["admin"],
},
user: {
name: "User Tools",
description: "标准用户操作",
modules: ["user"],
},
};
```
### 步骤 4:定义工具
```ts
const adminTool = {
name: "delete_user",
description: "删除用户账户",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "要删除的用户 ID" },
},
required: ["userId"],
},
handler: async ({ userId }: { userId: string }) => ({
content: [{ type: "text", text: `用户 ${userId} 已删除` }],
}),
} as const;
const userTool = {
name: "get_profile",
description: "获取用户信息",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "用户 ID" },
},
required: ["userId"],
},
handler: async ({ userId }: { userId: string }) => ({
content: [{ type: "text", text: `用户 ${userId} 的个人资料:{...}` }],
}),
} as const;
```
### 步骤 5:提供模块加载器
```ts
const moduleLoaders = {
admin: async () => [adminTool],
user: async () => [userTool],
};
```
### 步骤 6:选择权限方法
您有两种管理权限的选项:
**基于标头的权限:**
- 在您有身份验证网关/代理时使用
- 通过 HTTP 标头传递权限
- 适用于动态、频繁更改的权限
- 需要外部验证标头
**基于配置的权限:**
- 在您想要服务器端控制时使用
- 在服务器配置中定义权限
- 更好的安全性(无客户端提供的权限数据)
- 适用于稳定的权限结构
### 步骤 7:创建基于权限的 MCP 服务器
**选项 A:基于标头的权限**
```ts
const createServer = () =>
new McpServer({
name: "permission-header-server",
version: "1.0.0",
capabilities: { tools:Changed: false }const { start } = awaitBasedMcpServer({
catalog,
moduleLoaders,
permissions: {
source: "headers",
headerName: "mcp-toolset-permissions", // 可选,
},
http: { port: 3000 },
});
await start();
```
**选项 B:基于配置的权限(静态映射)**
```ts
const createServer = () =>
new McpServer({
name: "permission-config-server",
version: "1.0",
capabilities: { tools: { listChanged },
{ start, close await createPermissionBasedMcpServer({
moduleLoaders permissions: {
source: "config",
staticMap: {
"admin-client-id": ["admin", "user"],
"user-client-id": ["user"],
},
defaultPermissions: [], // 未知的客户端无工具集
},
http: { port: 3000 },
createServer,
});
await start();
```
**选项 C:基于配置的权限(解析器函数)**
```ts
const createServer = () =>
new McpServer({
name: "permission-resolver-server",
version: "1.0.0",
capabilities: { tools: { listChanged: false } },
});
const { start, close } = await createPermissionBasedMcpServer({
catalog,
moduleLoaders,
permissions: {
source: "config",
resolver: (clientId: string) => {
// 自定义权限逻辑
if (clientId.startsWith("admin-")) {
return ["admin", "user"];
}
if (clientId.startsWith("user-")) {
return ["user"];
}
return [];
},
defaultPermissions: [],
},
http: { port: 3000 },
createServer,
});
await start();
```
### 步骤 8:优雅关闭
```ts
process.on("SIGINT", async () => {
await close();
process.exit(0);
});
process.on("SIGTERM", async () => {
await close();
process.exit(});
```
## 权限配置方法
### 基于标头的权限设置
在您有身份验证网关或代理验证和设置权限标头时使用基于标头的权限。这种动态权限是灵活的,但需要外部标头验证。
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const createServer = () =>
new McpServer({
name: "permission-header-server",
version: "1.0.0",
capabilities: { tools: { listChanged: false } },
});
const { start, close } = awaitBasedMcpServer({
catalog: {
: {
name: "Admin",
description: "管理工具",
modules: ["admin"],
},
user: {
name: "User",
description: "用户工具",
modules: ["user"],
},
},
moduleLoaders: {
admin: async () => [
/* 管理工具 */
],
user: async () => [
/* 用户工具 */
],
},
source: "headers",
headerName: "mcp-toolset-permissions", //,默认为此值
},
http: { port: 3000 },
createServer,
});
await start();
```
**何时使用:**
- 您有验证请求的网关/- 权限频繁更改或按请求计算
- 您可以确保标头已加密签名或验证
- 您的身份验证系统独立于 MCP 服务器
### 基于配置的权限设置(静态映射)
使用静态映射,当您有一组具有已知权限的固定客户端时。这种方法提供服务器端控制和更好的安全性。
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const createServer = () =>
new McpServer({
: "permission-config-server",
version: "0.0",
capabilities: { listChanged } },
});
const { start, close } = await createPermissionBasedM({
{
admin name: "Admin",
description: "管理工具",
modules: ["admin"],
},
user: {
nameUser",
description: "用户工具 modules: [" },
},
Loaders: {
: async () /* 管理 ],
async () => [
工具 */
},
permissions source:",
staticMap "admin-client-id": ["admin", "user-client-id": [" },
default // 未知的客户端无
},
: { port:0 },
});
await```
**何时使用:**
- 您已知的固定客户端
- 权限相对 您想要最高- 您客户端提供的数据配置的权限设置(解析器函数)解析器函数,当您需要自定义逻辑来确定权限时,例如从数据库中应用复杂规则。
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { Mcp from "@modelcontextprotocol/sdk/server/mcp.js";
const createServer = () new McpServer({
namepermission-resolver-server",
version1.0.0",
{ tools: { listChanged: },
});
const { start, = await createPermissionBasedMcpServer({
catalog: {
admin: {
name: "Admin",
description: "管理工具",
modules: ["admin"],
},
user: {
name: "User",
description: "用户工具",
modules: ["user"],
},
},
moduleLoaders: {
admin: async () => [
/* 管理工具 */
],
user: async () => [
/* 用户工具 ],
},
permissions: {
: "config",
resolver: (clientId: string) => {
逻辑 - 可以检查数据库、配置文件等
if (clientId.startsWith("admin-")) {
return ["admin", "user"];
}
if (.startsWith("user-")) return ["user"];
}
},
staticMap: {
可选的备用静态映射
"special-client": ["admin"],
defaultPermissions: [],
},
{ port: 3000 },
createServer,
});
await start();
```
**何时使用:**
- 您需要自定义权限逻辑
- 权限基于客户端 ID 模式或属性计算
- 您想与现有的权限系统集成
- 您需要静态映射的备用行为
**注意:** 解析器函数必须是同步的。如果您需要从外部源获取权限,请在服务器创建之前执行此操作并缓存结果。
## 自定义 HTTP 端点
Toolception 支持在 MCP 协议端点之外的自定义 HTTP 端点,使您能够启用 REST 风格的 API,并使用 Zod 验证和类型推断。
### 基本用法
```ts
import { createMcpServer, defineEndpoint } from "toolception";
import { z } from "zod";
const { start } = await createMcpServer({
// ... 标准选项
http: {
port: 3000,
customEndpoints: [
defineEndpoint({
method: "GET",
path: "/api/users",
querySchema: z.object({
limit: z.coerce.number().int().positive().default(10),
role: z.enum(["admin", "user"]).optional(),
}),
responseSchema: z.object({
users: z.array(z.object({ id: z.string(), name: z.string() })),
}),
handler: async (req) => {
// req.query 类型为:{ limit: number, role?: "admin" | "user" }
// req.clientId 可从 mcp-client-id 标头获得
return { users: [{ id: "1", name: "Alice" }] };
},
}),
],
},
});
```
### 验证模式
- **querySchema**:验证 URL 查询参数(使用 `z.coerce` 进行类型转换)
- **bodySchema**:验证请求正文(POST/PUT/PATCH)
- **paramsSchema**:验证路径参数(例如 `/users/:userId`)
- **responseSchema**:验证处理程序响应(防止无效数据泄露)
### 请求上下文
处理器接收到一个类型化的请求对象:
```ts
{
body: TBody, // 从 bodySchema 验证
query: TQuery, // 从 querySchema 验证
params: TParams, // 从 paramsSchema 验证
headers: Record<string, string | string[] | undefined>,
clientId: string, // 从 mcp-client-id 头部或自动生成
}
```
### 基于权限的端点
在基于权限的服务器中使用 `definePermissionAwareEndpoint` 来访问客户端权限:
```ts
import { createPermissionBasedMcpServer, definePermissionAwareEndpoint } from "toolception";
const { start } = await createPermissionBasedMcpServer({
// ... 权限配置
http: {
customEndpoints: [
definePermissionAwareEndpoint({
method: "GET",
path: "/api/me",
responseSchema: z.object({
clientId: z.string(),
allowedToolsets: z.array(z.string()),
isAdmin: z.boolean(),
}),
handler: async (req) => {
// req.allowedToolsets 和 req.failedToolsets 可用
return {
clientId: req.clientId,
allowedToolsets: req.allowedToolsets,
isAdmin: req.allowedToolsets.includes("admin-tools"),
};
},
}),
],
},
});
```
### 错误处理
验证失败返回标准化的错误响应:
- **400 VALIDATION_ERROR**:请求验证失败(body、query 或 params)
- **500 INTERNAL_ERROR**:处理器抛出异常
- **500 RESPONSE_VALIDATION_ERROR**:响应验证失败
错误响应示例:
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed for query",
"details": [
{
"code": "invalid_type",
"path": ["limit"],
"message": "Expected number, received string"
}
]
}
}
```
### 保留路径
自定义端点不能覆盖内置的 MCP 路径:
- `/mcp` - MCP JSON-RPC 端点
- `/healthz` - 健康检查
- `/tools` - 工具列表
- `/.well-known/mcp-config` - 配置模式
### 完整示例
参见 `examples/custom-endpoints-demo.ts` 获取一个完整的示例,包括 GET、POST、PUT、DELETE 端点、分页和基于权限的处理器。
## 会话上下文
使用 `sessionContext` 选项启用从查询参数中提取的客户端上下文值。这对于多租户场景非常有用,每个客户端需要不同的配置(API 令牌、用户 ID 等)传递给模块加载器。
### 基本用法
```ts
import { createMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const { start } = await createMcpServer({
catalog: { /* ... */ },
moduleLoaders: { /* ... */ },
context: { baseValue: 'shared' }, // 所有会话的基本上下文
sessionContext: {
enabled: true,
queryParam: {
name: 'config',
encoding: 'base64',
allowedKeys: ['API_TOKEN', 'USER_ID'], // 安全:始终指定
},
merge: 'shallow',
},
createServer: () => new McpServer({
name: "my-server",
version: "1.0.0",
capabilities: { tools: { listChanged: true } },
}),
http: { port: 3000 },
});
await start();
```
### 客户端连接
```bash
# 将会话配置编码为 base64
CONFIG=$(echo -n '{"API_TOKEN":"user-secret-token","USER_ID":"123"}' | base64)
# 使用会话配置连接
curl -X POST "http://localhost:3000/mcp?config=$CONFIG" \
-H "mcp-client-id: my-client" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize",...}'
```
### 模块加载器接收合并的上下文
```ts
const moduleLoaders = {
tenant: async (ctx: any) => {
// ctx = { baseValue: 'shared', API_TOKEN: 'user-secret-token', USER_ID: '123' }
return [/* 使用 ctx.API_TOKEN 的工具 */];
},
};
```
### 自定义上下文解析器
对于高级用例,提供自定义解析器函数:
```ts
sessionContext: {
enabled: true,
queryParam: { allowedKeys: ['tenant_id'] },
contextResolver: (request, baseContext, parsedConfig) => ({
...baseContext,
...parsedConfig,
clientId: request.clientId,
timestamp: Date.now(),
}),
}
```
### 安全考虑
- **始终指定 `allowedKeys`**:如果没有白名单,查询配置中的任何键都将被接受
- **安全失败**:无效编码静默地回退到基本上下文
- **不记录值**:会话配置值从不记录
- **静默过滤**:不允许的键在没有错误消息的情况下静默过滤
## API
### createMcpServer(options)
将您的 MCP SDK 服务器连接到动态/静态工具管理和 Fastify HTTP 传输。
要求
- 必须提供 `createServer`。
- 在动态模式下,通过 `createServer` 为每个客户端创建一个新的服务器实例。
- 在静态模式下,通过 `createServer` 创建一个服务器实例,并为所有客户端重用。
#### options.catalog(必需)
`Record<string, ToolSetDefinition>`
- 定义要公开的可用工具集。每个项目包括 `name`、`description`、可选的内联 `tools`、可选的 `modules`(用于延迟加载器)和可选的 `decisionCriteria`。
#### options.moduleLoaders(可选)
`Record<string, ModuleLoader>`
- 将模块键映射到返回 `McpToolDefinition[]` 的异步加载器。工具集通过 `modules: [key]` 引用。
用法和行为
| 方面 | 详情 |
| --- | --- |
| 键命名 | 对象键是 `catalog[toolset].modules` 中引用的模块标识符。示例: `{ ext: async () => [...] }` 和 `modules: ["ext"]`。 |
| 加载器签名 | `(context?: unknown) => Promise<McpToolDefinition[]>` 或 `McpToolDefinition[]` |
| 何时调用 | 静态模式:在启动时(对于指定的工具集或所有工具集)。动态模式:当通过元工具启用工具集时。 |
| 返回值 | 要注册的工具数组。工具名称在每个工具集内应唯一;如果 `namespaceToolsWithSetKey` 为 true,则在注册时添加前缀。 |
| 错误 | 抛出拒绝该工具集的启用/预加载流程,并向调用者显示错误。 |
| 幂等性 | 加载器可能在多个运行/客户端之间调用多次。保持它们确定性/幂等。实现内部缓存,如果它们执行昂贵的 I/O 操作。 |
示例
```ts
const moduleLoaders = {
ext: async (ctx?: unknown) => [
{
name: "echo",
description: "回声文本",
inputSchema: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
handler: async ({ text }: { text: string }) => ({
content: [{ type: "text", text }],
}),
},
],
};
const catalog = {
ext: { name: "Extensions", description: "Extra tools", modules: ["ext"] },
};
```
#### options.startup(可选)
`{ mode?: "DYNAMIC" | "STATIC"; toolsets?: string[] | "ALL" }`
- 控制启动行为。在静态模式下,预加载特定的工具集(或所有工具集)。在动态模式下,注册元工具并按需加载。
启动优先级和验证
| 输入 | 有效模式 | 工具集处理 | 结果/备注 |
| --- | --- | --- | --- |
| `startup.mode = "DYNAMIC"`(存在或不存在工具集) | 动态 | 忽略 `startup.toolsets` | 在运行时通过元工具管理工具集;如果提供 `toolsets`,则记录警告 |
| `startup.mode = "STATIC"`,`toolsets = "ALL"` | 静态 | 预加载 `catalog` 中的所有工具集 | 确定 |
| `startup.mode = "STATIC"`,`toolsets = [names]` | 静态 | 验证 `catalog` 中的名称 | 无效名称警告;如果没有有效名称,则错误 |
| 没有 `startup.mode`,`toolsets = "ALL"` | 静态 | 预加载所有工具集 | 确定 |
| 没有 `startup.mode`,`toolsets = [names]` | 静态 | 验证 `catalog` 中的名称 | 无效名称警告;如果没有有效名称,则错误 |
| 没有 `startup.mode`,没有 `toolsets` | 动态 | 没有预加载 | 默认行为;在运行时通过元工具管理工具集 |
#### options.registerMetaTools(可选)
`boolean`(默认:动态模式为 true,静态模式为 false)
- 是否注册用于工具集管理的元工具。
- 在动态模式下:注册所有元工具(`enable_toolset`、`disable_toolset`、`list_toolsets`、`describe_toolset`、`list_tools`)。
- 在静态模式下:仅注册 `list_tools`(其他元工具不适用,因为工具集在启动时已固定)。
#### options.exposurePolicy(可选)
`ExposurePolicy`
- 控制哪些工具集可以激活以及注册工具时如何命名。
| 字段 | 类型 | 目的 | 示例 |
| --- | --- | --- | --- |
| `maxActiveToolsets` | `number` | 限制同时激活的工具集数量。防止工具膨胀。 | `{ maxActiveToolsets: 1 }` 阻止启用第二个工具集 |
| `namespaceToolsWithSetKey` | `boolean` | 在注册工具时,将工具名称前缀为工具集键,以避免名称冲突。 | 启用 `true`,则启用 `core` 注册 `core.ping` 而不是 `ping` |
| `allowlist` | `string[]` | 仅这些工具集可以启用。其他工具集被拒绝。 | `{ allowlist: ["core"] }` 阻止启用 `ext` |
| `denylist` | `string[]` | 这些工具集无法启用。 | `{ denylist: ["ext"] }` 阻止 `ext` |
| `onLimitExceeded` | `(attempted, active) => void` | 当超过 `maxActiveToolsets` 时的回调。 | 日志或遥测钩子 |
注意
- 在启用时(通过元工具或静态预加载)强制执行策略。
- 如果同时存在 `allowlist` 和 `denylist`,则条目必须存在于 `allowlist` 中,而不存在于 `denylist` 中。
#### options.context(可选)
`unknown`
- 传递给 `moduleLoaders` 以解析工具的任意上下文。
| 字段 | 类型 | 目的 | 示例 |
| --- | --- | --- | --- |
| `context` | `unknown` | 每个 `ModuleLoader(context)` 调用在解析工具时可用的额外数据/注入项。 | `{ db, cache, apiClients }` 在加载器中使用以构建工具 |
注意
- 只有 `moduleLoaders` 接收 `context`。直接在 `catalog` 中定义的工具不接收。
- 不通过 HTTP 向客户端公开;在服务器上保持进程内。
- 保持轻量级和稳定;优先传递句柄(例如,数据库客户端)而不是大型数据块。
#### options.sessionContext(可选)
`SessionContextConfig`
用于从查询参数中提取每个会话上下文的配置。启用多租户用例,每个客户端会话可以有自己的上下文值传递给模块加载器。有关详细用法示例,请参见 [Per-session context](#per-session-context)。
| 字段 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `enabled` | `boolean` | `true` | 是否启用会话上下文提取 |
| `queryParam.name` | `string` | `'config'` | 查询参数名称 |
| `queryParam.encoding` | `'base64' \| 'json'` | `'base64'` | 编码格式 |
| `queryParam.allowedKeys` | `string[]` | - | 允许的键的白名单(出于安全考虑) |
| `contextResolver` | `function` | - | 自定义上下文解析器函数 |
| `merge` | `'shallow' \| 'deep'` | `'shallow'` | 如何与基本上下文合并 |
注意
- 会话上下文按请求提取并与基本 `context` 选项合并
- 每个唯一的会话配置生成不同的缓存键,从而实现每个租户的模块缓存
- 无效编码或解析错误静默地回退到基本上下文(安全失败)
- 仅适用于动态模式服务器;静态模式使用单个共享服务器实例
#### options.http(可选)
`{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean; customEndpoints?: CustomEndpointDefinition[] }`
- Fastify 传输配置。默认值:host `0.0.0.0`,port `3000`,basePath `/`,启用 CORS,禁用日志记录。
- `customEndpoints`:可选的自定义 HTTP 端点数组,与 MCP 协议端点一起注册。有关详细信息,请参见 [Custom HTTP endpoints](#custom-http-endpoints)。
#### options.createServer(可选)
`() => McpServer`
创建 SDK 服务器实例的必要工厂。
#### options.configSchema(可选)
`object`
- 在 `GET /.well-known/mcp-config` 处公开的 JSON 模式,用于客户端发现。
### createPermissionBasedMcpServer(options)
创建基于权限的 MCP 服务器,其中每个客户端仅接收他们有权访问的工具集。该函数为基于权限的场景提供了一个单独的 API,同时保持与 `createMcpServer` 相同的接口。
要求
- 必须提供 `createServer`
- 必须提供 `permissions` 配置
- 服务器在每个客户端静态模式下运行(工具集由权限决定)
- 每个客户端获取一个具有其特定工具集的隔离服务器实例
#### 选项.permissions (必需)
`PermissionConfig`
定义客户端权限如何解析和强制执行。
**权限来源类型**
| 来源 | 描述 | 用例 | 安全级别 |
| --------- | ------------------------------------- | ---------------------------------- | ------------------------------------- |
| `headers` | 从请求头读取权限 | 在认证代理/网关后面 | 中等(需要外部验证) |
| `config` | 服务器端权限查找 | 直接服务器控制 | 高(服务器控制) |
**基于头的配置**
| 字段 | 类型 | 默认值 | 描述 |
| ------------ | ----------- | --------------------------- | --------------------------------------------------- |
| `source` | `'headers'` | 必需 | 表示基于头的权限 |
| `headerName` | `string` | `'mcp-toolset-permissions'` | 包含逗号分隔的工具集列表的头名称 |
**基于配置的配置**
| 字段 | 类型 | 必需 | 描述 |
| -------------------- | -------------------------------- | ---------------------------- | -------------------------------------------------------- |
| `source` | `'config'` | 是 | 表示基于配置的权限 |
| `staticMap` | `Record<string, string[]>` | `staticMap` 或 `resolver` 之一 | 将客户端 ID 映射到工具集数组 |
| `resolver` | `(clientId: string) => string[]` | `staticMap` 或 `resolver` 之一 | 返回客户端的工具集数组 |
| `defaultPermissions` | `string[]` | 否 | 未知客户端的默认权限(默认为 `[]`) |
**注意**
- 对于基于配置的权限,必须提供 `staticMap` 或 `resolver` 之一
- 如果两者都提供,则先尝试 `resolver`,然后是 `staticMap`,最后是 `defaultPermissions`
- 解析器函数必须是同步的,并返回字符串数组
- 无效的权限中的工具集名称在服务器创建期间被过滤掉
#### 选项.catalog (必需)
与 `createMcpServer` 相同 - 请参阅 [options.catalog](#optionscatalog-必需).
#### 选项.moduleLoaders (可选)
与 `createMcpServer` 相同 - 请参阅 [options.moduleLoaders](#optionsmoduleloaders-可选).
#### 选项.exposurePolicy (可选)
`ExposurePolicy`(部分支持)
基于权限的服务器仅支持 `namespaceToolsWithSetKey`。其他策略字段被忽略,因为工具集访问由权限控制:
| 字段 | 支持 |
| -------------------------- | ------------------------------------------------- |
| `namespaceToolsWithSetKey` | ✅ 支持(默认:true) |
| `allowlist` | ⚠️ 忽略(由客户端权限决定) |
| `denylist` | ⚠️ 忽略(使用权限代替) |
| `maxActiveToolsets` | ⚠️ 忽略(由权限计数决定) |
| `onLimitExceeded` | ⚠️ 忽略(不强制执行工具集限制) |
**注意:** 如果您提供被忽略的选项,服务器将在启动时记录警告以提醒您。
#### 选项.http (可选)
与 `createMcpServer` 相同 - 请参阅 [options.http](#optionshttp-可选).
#### 选项.createServer (必需)
与 `createMcpServer` 相同 - 请参阅 [options.createServer](#optionscreateserver-可选).
#### 选项.configSchema (可选)
与 `createMcpServer` 相同 - 请参阅 [options.configSchema](#optionsconfigschema-可选).
#### 选项.context (可选)
与 `createMcpServer` 相同 - 请参阅 [options.context](#optionscontext-可选).
#### 选项.sessionContext (可选)
`SessionContextConfig`
会话上下文在基于权限的服务器中可用,但支持有限。由于基于权限的服务器在连接时根据权限确定工具集,因此会话上下文不能影响加载哪些工具集。但是,合并的上下文仍传递给模块加载器。
**注意:** 如果 `sessionContext` 与 `createPermissionBasedMcpServer` 一起使用,则会发出警告。对于具有每会话工具集缓存的完整会话上下文支持,请使用 `createMcpServer` 并启用 DYNAMIC 模式。
### 元工具
元工具根据模式注册:
**DYNAMIC 模式**(默认注册,或当 `registerMetaTools` 为 true 时):
- `enable_toolset` - 按名称启用工具集
- `disable_toolset` - 按名称禁用工具集(仅状态;工具仍已注册)
- `list_toolsets` - 列出具有活动状态的可用工具集
- `describe_toolset` - 描述具有定义和工具的特定工具集
- `list_tools` - 列出当前注册的工具名称
**STATIC 模式**(当 `registerMetaTools` 为 true 时):
- `list_tools` - 列出当前注册的工具名称
注意:在 STATIC 模式下,`enable_toolset`、`disable_toolset`、`list_toolsets` 和 `describe_toolset` 不可用,因为工具集在启动时已固定。
## 基于权限的客户端集成
### 使用基于头的权限
连接到具有基于头的权限的基于权限的服务器时,请在 `mcp-toolset-permissions` 头中包含一个逗号分隔的工具集列表:
```ts
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const clientId = "my-client-id";
const allowedToolsets = ["user", "reports"]; // 由您的身份验证系统确定
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
{
requestInit: {
headers: {
"mcp-client-id": clientId,
"mcp-toolset-permissions": allowedToolsets.join(","),
},
},
}
);
const client = new Client({ name: "example-client", version: "1.0.0" });
await client.connect(transport);
// 客户端只能访问 'user' 和 'reports' 工具集中的工具
const tools = await client.listTools();
console.log(tools); // 仅显示 user.* 和 reports.* 工具
await client.close();
```
**重要:** 您的应用层必须验证并可能签名/加密权限头以防止篡改。MCP 服务器信任头值原样。
### 使用基于配置的权限
连接到具有基于配置的权限的基于权限的服务器时,只需提供 `mcp-client-id` 头。服务器在内部查找权限:
```ts
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const clientId = "admin-client-id"; // 与服务器的 staticMap 或 resolver 匹配
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
{
requestInit: {
headers: {
"mcp-client-id": clientId,
// 不需要权限头 - 服务器查找权限
},
},
}
);
const client = new Client({ name: "example-client", version: "1.0.0" });
await client.connect(transport);
// 客户端根据服务器配置接收工具集
const tools = await client.listTools();
console.log(tools); // 显示服务器权限配置中的工具
await client.close();
```
**安全:** 基于配置的权限提供更好的安全性,因为客户端无法影响其自身的权限。确保您的客户端 ID 在到达 MCP 服务器之前已身份验证和验证。
## 客户端 ID 生命周期
- **是什么:** 客户端通过每个请求中的 `mcp-client-id` HTTP 头标识自己。
- **谁生成:** 客户端。使用稳定的标识符(例如,本地持久化的 UUID)。
- **如果省略:** MCP 协议端点(`POST /mcp`、`GET /mcp`、`DELETE /mcp`)返回 400 错误。自定义端点仍然接受匿名客户端,并使用自动生成的 ID。
示例(官方 MCP 客户端)
```ts
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
// 创建一个稳定的客户端 ID(为重用而持久化)
const clientId = "my-stable-client-id"; // 例如,从磁盘/环境
// 传输管理 HTTP,包括 SSE 和 JSON-RPC 分帧
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
{
requestInit: { headers: { "mcp-client-id": clientId } },
}
);
// 高级 MCP 客户端
const client = new Client({ name: "example-client", version: "1.0.0" });
// 连接协商功能并建立会话。传输处理会话 ID。
await client.connect(transport);
// 调用工具(示例)
const res = await client.listTools();
console.log(res);
// 完成后关闭
await client.close();
```
## 会话 ID 生命周期
- **是什么:** 服务器在初始化时返回的每个会话的标识符。
- **谁生成:** 服务器在初始化期间生成。客户端必须从初始化响应头中读取它,并在后续请求中通过 `mcp-session-id` 发送回来。
- **用于:** 后续 JSON-RPC 请求(`POST /mcp`)、SSE 流(`GET /mcp`)和终止(`DELETE /mcp`)。
示例(官方 MCP 客户端)
```ts
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const clientId = "my-stable-client-id";
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
{
requestInit: { headers: { "mcp-client-id": clientId } },
}
);
const client = new Client({ name: "example-client", version: "1.0.0" });
await client.connect(transport);
// 会话 ID 由传输处理。无需手动设置 mcp-session-id。
// 调用工具
await client.callTool({ name: "enable_toolset", arguments: { name: "core" } });
const ping = await client.callTool({ name: "core.ping", arguments: {} });
console.log(ping);
// 完成后
await client.close();
```
## 基于权限的安全最佳实践
### 何时使用每种方法
**使用基于头的权限:**
- 您有一个身份验证网关/代理来验证和设置头
- 您需要频繁更改的动态权限
- 您的身份验证系统在 MCP 服务器外部
- 您可以确保头被加密签名或验证
**使用基于配置的权限:**
- 您希望对权限进行服务器端控制
- 权限相对稳定
- 您需要最高的安全级别
- 您希望避免信任客户端提供的数据
### 身份验证和授权模式
**基于头的模式:**
```
客户端 → 身份验证网关 → MCP 服务器
(验证,
设置头)
```
身份验证网关必须:
1. 验证客户端
2. 确定授权的工具集
3. 设置 `mcp-toolset-permissions` 头
4. 可选地签名/加密头以防止篡改
**基于配置的模式:**
```
客户端 → MCP 服务器 → 权限查找
(验证 (静态映射或
客户端 ID) 解析器)
```
MCP 服务器:
1. 接收客户端 ID
2. 内部查找权限
3. 不信任客户端提供的权限数据
### 头验证和签名
如果使用基于头的权限,请实施验证以防止篡改:
```ts
import crypto from "crypto";
// 示例:使用 HMAC 签名权限头
function signPermissions(
clientId: string,
toolsets: string[],
secret: string
): string {
const data = `${clientId}:${toolsets.join(",")}`;
const signature = crypto
.createHmac("sha256", secret)
.update(data)
.digest("hex");
return `${toolsets.join(",")};sig=${signature}`;
}
function verifyPermissions(
clientId: string,
headerValue: string,
secret: string
): string[] {
const [toolsetsStr, sigPart] = headerValue.split(";sig=");
const expectedSig = crypto
.createHmac("sha256", secret)
.update(`${clientId}:${toolsetsStr}`)
.digest("hex");
if (sigPart !== expectedSig) {
throw new Error("Invalid permission signature");
}
return toolsetsStr.split(",").map((s) => s.trim());
}
// 在您的身份验证网关中:
const clientId = "user-123";
const allowedToolsets = ["user", "reports"];
const signedHeader = signPermissions(clientId, allowedToolsets, SECRET_KEY);
// 转发到 MCP 服务器并带有签名头
fetch("http://mcp-server:3000/mcp", {
headers: {
"mcp-client-id": clientId,
"mcp-toolset-permissions": signedHeader,
},
});
```
### 安全考虑
**基于头的权限:**
- **风险:** 客户端如果没有正确保护,可能潜在地操纵头
- **缓解措施:** 总是在您的应用层验证/签名头部
- **推荐:** 仅在身份验证的反向代理或网关后面使用
- **最佳实践:** 使用 HMAC 或 JWT 实现头部签名
**基于配置的权限:**
- **好处:** 服务器端权限存储提供更强的安全性
- **推荐:** 生产环境首选
- **最佳实践:** 在 MCP 服务器到达之前验证和身份验证客户端 ID
- **注意:** 没有客户端提供的权限数据暴露
**一般安全:
- **权限缓存:** 权限按客户端会话缓存。当权限更改时,失效会话。
- **客户端隔离:** 每个客户端获得一个隔离的服务器实例。无跨客户端权限泄漏。
- **错误消息:** 服务器在错误响应中避免暴露未授权的工具集名称。
- **客户端 ID 验证:** 总是在您的应用层验证和身份验证客户端 ID,然后再让请求到达 MCP 服务器。
### 错误处理和信息披露
当客户端尝试访问未授权的工具集时:
- 服务器返回一个通用的“拒绝访问”错误
- 错误消息中不暴露未授权的工具集名称
- 这防止了有关可用工具集的信息泄露
- 客户端只能通过 `listTools()` 查看他们有权访问的工具
## 基于权限的常见模式
### 多租户服务器设置
创建一个服务器,其中每个租户都可以访问自己的工具集以及共享工具:
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const { start, close } = await createPermissionBasedMcpServer({
catalog: {
"tenant-a-tools": {
name: "Tenant A",
description: "Tools for tenant A",
modules: ["tenant-a"],
},
"tenant-b-tools": {
name: "Tenant B",
description: "Tools for tenant B",
modules: ["tenant-b"],
},
"shared-tools": {
name: "Shared",
description: "Shared tools",
modules: ["shared"],
},
},
moduleLoaders: {
"tenant-a": async () => [
/* tenant A specific tools */
],
"tenant-b": async () => [
/* tenant B specific tools */
],
shared: async () => [
/* shared tools */
],
},
permissions: {
source: "config",
resolver: (clientId: string) => {
const [tenant] = clientId.split("-");
if (tenant === "tenantA") {
return ["tenant-a-tools", "shared-tools"];
}
if (tenant === "tenantB") {
return ["tenant-b-tools", "shared-tools"];
}
return ["shared-tools"]; // unknown tenants get only shared tools
},
},
http: { port: 3000 },
createServer: () =>
new McpServer({
name: "multi-tenant-server",
version: "1.0.0",
capabilities: { tools: { listChanged: false } },
}),
});
await start();
```
### 与外部身份验证系统集成
通过预加载权限与外部身份验证系统集成:
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// 预加载权限来自您的身份验证系统
// 这应该在服务器创建之前完成并缓存
const permissionCache = new Map<string, string[]>();
async function loadPermissionsFromAuthSystem() {
// 从您的身份验证系统获取权限
// 这只是示例 - 根据您的系统实现
const users = await authSystem.getAllUsers();
for (const user of users) {
const permissions = await authSystem.getUserPermissions(user.id);
permissionCache.set(user.id, permissions.allowedToolsets);
}
}
// 在启动时加载权限
await loadPermissionsFromAuthSystem();
// 可选地,定期刷新权限
setInterval(loadPermissionsFromAuthSystem, 5 * 60 * 1000); // 每 5 分钟
const { start, close } = await createPermissionBasedMcpServer({
catalog: {
/* 您的工具集 */
},
moduleLoaders: {
/* 您的加载器 */
},
permissions: {
source: "config",
resolver: (clientId: string) => {
// 从预加载的缓存同步查找
return permissionCache.get(clientId) || [];
},
defaultPermissions: ["public"], // 未身份验证的用户获取公共工具
},
http: { port: 3000 },
createServer: () =>
new McpServer({
name: "auth-integrated-server",
version: "1.0.0",
capabilities: { tools: { listChanged: false } },
}),
});
await start();
```
### 基于角色的访问控制(RBAC)
实现基于角色的访问控制,并具有预定义的角色到工具集的映射:
```ts
import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// 定义角色到工具集的映射
const rolePermissions = {
admin: ["admin-tools", "user-tools", "reports", "analytics"],
manager: ["user-tools", "reports", "analytics"],
user: ["user-tools", "reports"],
guest: ["public-tools"],
};
// 将客户端 ID 映射到角色(可以来自数据库、JWT 声明等)
function getRoleForClient(clientId: string): string {
// 示例:从客户端 ID 中提取角色或在数据库中查找
if (clientId.startsWith("admin-")) return "admin";
if (clientId.startsWith("manager-")) return "manager";
if (clientId.startsWith("user-")) return "user";
return "guest";
}
const { start, close } = await createPermissionBasedMcpServer({
catalog: {
"admin-tools": {
name: "管理员工具",
description: "管理员工具",
modules: ["admin"],
},
"user-tools": {
name: "用户工具",
description: "用户工具",
modules: ["user"],
},
reports: {
name: "报告工具",
description: "报告工具",
modules: ["reports"],
},
analytics: {
name: "分析工具",
description: "分析工具",
modules: ["analytics"],
},
"public-tools": {
name: "公共工具",
description: "公共工具",
modules: ["public"],
},
},
moduleLoaders: {
admin: async () => [
/* 管理员工具 */
],
user: async () => [
/* 用户工具 */
],
reports: async () => [
/* 报告工具 */
],
analytics: async () => [
/* 分析工具 */
],
public: async () => [
/* 公共工具 */
],
},
permissions: {
source: "config",
staticMap: {
// 已知管理员用户
"admin-user-1": rolePermissions.admin,
"admin-user-2": rolePermissions.admin,
// 已知经理
"manager-user-1": rolePermissions.manager,
// 已知普通用户
"regular-user-1": rolePermissions.user,
"regular-user-2": rolePermissions.user,
},
resolver: (clientId: string) => {
// 动态角色查找客户端不在静态映射中
const role = getRoleForClient(clientId);
return rolePermissions[role] || rolePermissions.guest;
},
defaultPermissions: rolePermissions.guest,
},
http: { port: 3000 },
createServer: () =>
new McpServer({
name: "rbac-server",
version: "1.0.0",
capabilities: { tools: { listChanged: false } },
}),
});
await start();
```
## 工具类型
- 直接工具:直接在 `catalog[toolset].tools` 下定义,并在启用该工具集时注册。
- 模块生成的工具:由 `moduleLoaders[moduleKey]()` 返回,并在启用引用 `modules: [moduleKey]` 的工具集时注册。
对于简单的本地实用程序,请使用直接工具;对于跨多个工具集共享工具或惰性加载较重的定义,请使用模块生成的工具。
关于动态模式的说明:支持直接工具和模块生成的工具。模块生成的工具通过在启用时按需加载来帮助最小化启动占用。
## 启动模式
服务器以两种主要模式之一运行:
1. **动态模式** (`startup.mode = "DYNAMIC"`)
- 所有元工具已注册:`enable_toolset`、`disable_toolset`、`list_toolsets`、`describe_toolset`、`list_tools`
- 工具按需通过元工具调用加载
- 每个客户端都获得一个隔离的服务器实例
- 适用于灵活的、任务特定的工作流程,其中工具需求会发生变化
2. **静态模式** (`startup.mode = "STATIC"`)
- 在启动时预加载特定的工具集(`toolsets` 数组或 "ALL")
- 仅 `list_tools` 元工具可用(工具集在运行时无法更改)
- 为所有客户端重用单个服务器实例
- 适用于已知的、一致的工具需求
## 许可证
Apache-2.0。有关详细信息,请参见 `LICENSE`。
连接信息
猜你喜欢
everything-claude-code
完整的 Claude Code 配置集合 - agents、skills、hooks、commands、rules、MCPs。来自 Anthropic...
Firecrawl
Firecrawl MCP Server 实现了网页抓取、爬虫和内容提取功能。
MarkItDown MCP
MarkItDown-MCP是一个轻量级服务器,用于将URI转换为Markdown。
cc-switch
跨平台的Claude Code、Codex和Gemini CLI一体化助手。
Agent-Reach
赋予你的AI Agent观察整个互联网的眼睛。读取并搜索...
servers
# 模型上下文协议服务器