返回市场
工具嵌套

工具嵌套

作者:code-rabi2 星标更新:2025-10-09

项目介绍

Toolception – 动态MCP工具库

npm 版本 许可证

目录

何时以及为何使用Toolception

构建具有数十或数百个工具的MCP服务器通常会对LLM性能和开发者体验造成负面影响:

  • 过多的工具使选择变得困难:更大的工具列表增加了混淆和误选率。
  • 令牌和模式膨胀:长工具目录会增加提示和延迟。
  • 命名冲突和模糊性:跨域相似的工具名称会导致失败和脆弱的集成。
  • 操作开销:提前加载每个工具浪费资源;许多工具是任务特定的。

Toolception通过将工具分组到工具集中,并让您仅在需要时暴露所需内容来解决这些问题。

何时使用Toolception

  • 大型或多领域目录:您有超过20-50个工具或多个领域(例如搜索、数据、计费),并且不想一次性暴露它们。
  • 任务特定的工作流:您希望客户端/代理仅启用与当前任务相关的工具。
  • 多租户或策略需求:不同的用户/租户需要不同的工具访问或限制。
  • 基于权限的访问控制:您需要对客户端特定的工具集权限进行强制执行,以确保安全、合规或隔离多租户。每个客户端应仅看到并访问其被授权使用的工具集,并通过服务器端或基于头部的权限强制执行。
  • 避免冲突的命名:您需要可预测的命名空间工具名称以避免冲突。
  • 懒加载:某些工具很重,应该按需加载。

Toolception如何帮助

  • 工具集:将相关工具分组,并根据任务暴露最小且连贯的子集。
  • 动态模式(运行时控制)
    • 通过元工具(如enable_toolsetdisable_toolsetlist_toolsetsdescribe_toolsetlist_tools)按需启用工具集。
    • 减少提示/工具表面区域 → 更好的工具选择和更低的延迟。
    • 按需懒加载模块生成的工具;安全地将共享context传递给加载器。
    • 支持tools.listChanged通知,以便客户端可以响应更新的工具列表。
  • 静态模式(可预测的启动)
    • 启动时预加载已知的工具集(或全部),适用于固定管道和简单的环境。
    • 仅保留部署所需的集合。
  • 暴露策略
    • maxActiveToolsets:限制同时活跃的集合数量,防止膨胀。
    • allowlist/denylist:强制执行哪些工具集可以启用。
    • namespaceToolsWithSetKey:默认开启;注册工具为set.tool以避免冲突并明确意图。
  • 操作安全性
    • 中央ToolRegistry验证名称并防止冲突。
    • ModuleLoaders是确定性的/幂等的,用于重复运行和缓存。

选择模式

  • 优先选择DYNAMIC:当工具需求因任务而异,您想要更紧凑的提示,或者您需要运行时门控和懒加载时。
  • 选择STATIC:当您的工具需求稳定且较小,或者您的客户端不能(或不应)执行运行时启用/禁用操作时。

典型流程

  • 发现优先(动态):客户端调用list_toolsets → 启用一个集合 → 调用命名空间工具(例如core.ping)。
  • 固定管道(静态):服务器在启动时预加载命名工具集(或全部);客户端调用list_tools并按常规方式调用。

入门指南

第一步:安装

npm i toolception

第二步:导入Toolception

import { createMcpServer } from "toolception";

第三步:定义工具集目录

const catalog = {
  quotes: { name: "Quotes", description: "市场报价", modules: ["quotes"] },
};

第四步:定义工具

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;

第五步:提供模块加载器

const moduleLoaders = {
  quotes: async () => [quoteTool],
};

第六步:(可选)配置模式

const configSchema = {
  $schema: "https://json-schema.org/draft/2020-12/schema",
  type: "object",
  properties: {
    REQUIRED_PARAM: { type: "string", title: "必需参数" },
    OPTIONAL_PARAM: { type: "string", title: "可选参数" },
  },
  required: ["REQUIRED_PARAM"],
} as const;

第七步:创建MCP SDK服务器并启动Toolception

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

// 您拥有SDK服务器;将工厂传入Toolception(在DYNAMIC模式下需要)
const createServer = () =>
  new McpServer({
    name: "my-mcp-server",
    version: "0.0.0",
    capabilities: { tools: { listChanged: true } },
  });

const { start, close } = await createMcpServer({
  catalog,
  moduleLoaders,
  startup: { mode: "DYNAMIC" },
  http: { port: 3000 },
  createServer,
  // configSchema, // 注释掉以在/.well-known/mcp-config中暴露
});
await start();

第八步:优雅关闭

process.on("SIGINT", async () => {
  await close();
  process.exit(0);
});
process.on("SIGTERM", async () => {
  await close();
  process.exit(
    0);
});

静态启动

在引导时启用一些或全部工具集。注意:提供一个服务器或工厂:

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 },
  server: new McpServer({
    name: "static-1",
    version: "0.0.0",
    capabilities: { tools: { listChanged: false } },
  }),
});

createMcpServer({
  catalog: staticCatalog,
  startup: { mode: "STATIC", toolsets: "ALL" },
  http: { port: 3002 },
  server: new McpServer({
    name: "static-2",
    version: "0.0.0",
    capabilities: { tools: { listChanged: false } },
  }),
});

基于权限的入门指南

使用createPermissionBasedMcpServer当您需要强制执行客户端特定的工具集权限时。这对于多租户应用程序、安全敏感的环境或不同客户端应有不同的访问级别时非常理想。

第一步:安装

npm i toolception

第二步:导入Toolception

import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

第三步:定义工具集目录

const catalog = {
  admin: {
    name: "管理工具",
    description: "管理操作",
    modules: ["admin"],
  },
  user: {
    name: "用户工具",
    description: "标准用户操作",
    modules: ["user"],
  },
};

第四步:定义工具

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;

第五步:提供模块加载器

const moduleLoaders = {
  admin: async () => [adminTool],
  user: async () => [userTool],
};

第六步:选择权限方法

您有两种管理权限的方法:

基于头部的权限:

  • 当您有一个认证网关/代理时使用
  • 权限通过HTTP头部传递
  • 适合动态、频繁变化的权限
  • 需要外部验证头部

基于配置的权限:

  • 当您想在服务器端控制时使用
  • 权限在服务器配置中定义
  • 更高的安全性(没有客户端提供的权限数据)
  • 适合稳定的权限结构

第七步:创建基于权限的MCP服务器

选项A:基于头部的权限

const createServer = () =>
  new McpServer({
    name: "permission-header-server",
    version: "1.0.0",
    capabilities: { tools: { listChanged: false } },
  });

const { start, close } = await createPermissionBasedMcpServer({
  catalog,
  moduleLoaders,
  permissions: {
    source: "headers",
    headerName: "mcp-toolset-permissions", // 可选,默认值
  },
  http: { port: 3000 },
  createServer,
});

await start();

选项B:基于配置的权限(静态映射)

const createServer = () =>
  new McpServer({
    name: "permission-config-server",
    version: "1.0.0",
    capabilities: { tools: { listChanged: false } },
  });

const { start, close } = await createPermissionBasedMcpServer({
  catalog,
  moduleLoaders,
  permissions: {
    source: "config",
    staticMap: {
      "admin-client-id": ["admin", "user"],
      "user-client-id": ["user"],
    },
    defaultPermissions: [], // 未知客户端没有任何工具集
  },
  http: { port: 3000 },
  createServer,
});

await start();

选项C:基于配置的权限(解析函数)

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();

第八步:优雅关闭

process.on("SIGINT", async () => {
  await close();
  process.exit(0);
});

process.on("SIGTERM", async () => {
  await close();
  process.exit(0);
});

权限配置方法

基于头部的权限设置

当您有一个验证请求的认证网关或代理时使用基于头部的权限。这种方法对于动态权限非常灵活,但需要外部头部验证。

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 } = await createPermissionBasedMcpServer({
  catalog: {
    admin: {
      name: "Admin",
      description: "管理员工具",
      modules: ["admin"],
    },
    user: {
      name: "User",
      description: "用户工具",
      modules: ["user"],
    },
  },
  moduleLoaders: {
    admin: async () => [
      /* 管理员工具 */
    ],
    user: async () => [
      /* 用户工具 */
    ],
  },
  permissions: {
    source: "headers",
    headerName: "mcp-toolset-permissions", // 可选,默认值
  },
  http: { port: 3000 },
  createServer,
});

await start();

何时使用:

  • 您有一个验证请求的认证网关/代理
  • 权限频繁更改或按请求计算
  • 您可以确保头部经过加密签名或验证
  • 您的认证系统位于MCP服务器之外

基于配置的权限设置(静态映射)

当您有一组固定的已知客户端及其已知权限时使用静态映射。这提供了服务器端控制和更高的安全性。

import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const createServer = () =>
  new McpServer({
    name: "permission-config-server",
    version: "1.0.0",
    capabilities: { tools: { listChanged: false } },
  });

const { start, close } = await createPermissionBasedMcpServer({
  catalog: {
    admin: {
      name: "Admin",
      description: "管理员工具",
      modules: ["admin"],
    },
    user: {
      name: "User",
      description: "用户工具",
      modules: ["user"],
    },
  },
  moduleLoaders: {
    admin: async () => [
      /* 管理员工具 */
    ],
    user: async () => [
      /* 用户工具 */
    ],
  },
  permissions: {
    source: "config",
    staticMap: {
      "admin-client-id": ["admin", "user"],
      "user-client-id": ["user"],
    },
    defaultPermissions: [], // 不在映射中的客户端没有任何工具集
  },
  http: { port: 3000 },
  createServer,
});

await start();

何时使用:

  • 您有一组固定的已知客户端
  • 权限相对稳定
  • 您想要最高级别的安全性
  • 您想要避免信任客户端提供的数据

基于配置的权限设置(解析函数)

当您需要自定义逻辑来确定权限时使用解析函数,例如从数据库查找或应用复杂规则。

import { createPermissionBasedMcpServer } from "toolception";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const createServer = () =>
  new McpServer({
    name: "permission-resolver-server",
    version: "1.0.0",
    capabilities: { tools: { listChanged: false } },
  });

const { start, close } = await createPermissionBasedMcpServer({
  catalog: {
    admin: {
      name: "Admin",
      description: "管理员工具",
      modules: ["admin"],
    },
    user: {
      name: "User",
      description: "用户工具",
      modules: ["user"],
    },
  },
  moduleLoaders: {
    admin: async () => [
      /* 管理员工具 */
    ],
    user: async () => [
      /* 用户工具 */
    ],
  },
  permissions: {
    source: "config",
    resolver: (clientId: string) => {
      // 自定义逻辑 - 可能检查数据库、配置文件等
      if (clientId.startsWith("admin-")) {
        return ["admin", "user"];
      }
      if (clientId.startsWith("user-")) {
        return ["user"];
      }
      return [];
    },
    staticMap: {
      // 可选的后备
      "special-client": ["admin"],
    },
    defaultPermissions: [],
  },
  http: { port: 3000 },
  createServer,
});

await start();

何时使用:

  • 您需要自定义权限逻辑
  • 权限基于客户端ID模式或属性计算
  • 您想要与现有的权限系统集成
  • 您需要使用静态映射作为后备行为

注意: