首页 / 文章

为代理型MCP服务器设计高令牌效率的工具界面

了解如何将数十个MCP工具定义整合为少数几个以领域为导向、按操作路径组织的工具,同时不丢失任何底层功能。

发布者:REACTAPP.TOP阅读约 19 分钟4404 词

来源: **From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface**

现代智能体系统面临的典型问题并非工具短缺。

而是工具数量过多。

随着智能体功能的扩展,人们通常会将其与Jira、GitLab、Confluence、Sentry、Elasticsearch、Jaeger、各类数据库、监控面板、内部API、部署流水线以及无数其他系统相连。

每一种这样的集成往往都会带来大量操作选项:

Jira
├── searchIssues
├── getIssue
├── createIssue
├── updateIssue
├── addComment
├── transitionIssue
├── getTransitions
├── assignIssue
└── ...

GitLab
├── listProjects
├── getProject
├── listIssues
├── createIssue
├── updateIssue
├── listMergeRequests
├── getMergeRequest
├── createComment
└── ...

Confluence
├── searchPages
├── getPage
├── createPage
├── updatePage
└── ...

不久之后,你就需要维护30个、50个甚至数百个工具定义。

乍看之下,这似乎是功能过剩的问题。

实际上,这是工具表面问题。

智能体并不需要能做的功能更少。

它需要的是可供选择的顶层工具更少。

本文介绍了一种方法,可将臃肿的MCP或工具界面精简为少数几个专注于特定领域的工具,同时不会丧失任何底层功能。

核心思想很简单:

应缩小工具界面,而非功能集合。

不必暴露诸如以下内容:

30 MCP tools

而是可以暴露:

3–5 domain tools

并依靠action字段在内部路由每个调用。

这样一来,像这样的集合:

jira_search
jira_get_issue
jira_create_issue
jira_update_issue
jira_add_comment
jira_transition_issue
...

就会简化为类似这样的形式:

jira({
  action: "search",
  ...
})

jira({
  action: "getIssue",
  ...
})

jira({
  action: "createIssue",
  ...
})

后端仍保留所有原有功能。

但从模型的角度来看,工具界面则大幅缩小了。

1. 工具界面过大的问题

MCP服务器并非单纯的执行机制。

它也会成为模型需要保持在上下文中的内容之一。

每当智能体连接到MCP服务器时,模型通常需要处理:

  • 工具名称
  • 描述信息
  • 输入结构
  • 参数
  • 枚举值
  • 约束条件
  • 有时还包括额外的元数据

想象一个连接了40种不同工具的智能体。

即便每一种结构的规模都很小,总体体积仍会迅速增加。

除此之外,模型现在还面临着更复杂的决策问题:

User request
     │
     ▼
Which tool?
     │
 ┌───┼────┬────┬────┬────┐
 ▼   ▼    ▼    ▼    ▼    ▼
T1   T2   T3   T4   T5   ...

它必须区分许多在概念上相近的操作。

以这个例子为例:

jira_search_issues
jira_search_projects
jira_get_issue
jira_get_issue_comments
jira_get_issue_transitions
jira_get_issue_worklogs

这些都属于同一个概念范畴。

将每一个都视为完全独立的顶层功能并不会带来太多价值。

2. 核心理念:工具复用

关键在于添加中间层,即一个区分字段:

{
  "action": "search",
  "query": "authentication bug"
}

而不是定义:

jira_search

你只需暴露:

jira

该工具本身便成为了一个轻量级的路由器。

从结构上看,它类似于:

jira
                      │
                 action field
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
    search         getIssue       createIssue
       │              │              │
       ▼              ▼              ▼
 searchHandler   issueHandler   createHandler

核心要点在于工具与功能并非同一概念。

工具仅仅是面向外部的接口。

实际承载功能的是动作字段。

其结果为:

30 capabilities
       ↓
3 domain-oriented tools

整个过程中不会丢失任何功能。

3. 使用领域边界,而非随意分组

一个常见的误区是如下这种思维方式:

“我有30个工具,那就把10种操作打包到每个工具中。”

这并非正确的做法。

分组应基于语义领域来决定,而非任意的分类规模。

例如:

jira
├── search
├── issue
├── comment
├── transition
└── project

gitlab
├── project
├── issue
├── mergeRequest
└── pipeline

observability
├── search
├── trace
├── log
└── error

不同的集成方式需要采用不同的分组方式。

对于数据库,类似这样的分组:

db_tables
db_query
db_advanced

可能会很适用。

对于Jira,你可能更倾向于:

jira_issues
jira_projects
jira_search

而对于GitLab,则适合采用如下布局:

gitlab_repository
gitlab_issues
gitlab_mergeRequests

这样的结构可能更合适。

并不存在适用于所有情况的固定数值。

目标是在保持清晰、有意义的语义边界的前提下,找到最简洁的工具结构。

4. 数据库示例

数据库连接器是很好的说明案例,因为数据库API通常会暴露大量独立的操作。

无需为每个操作定义单独的条目,比如:

listTables
getTableSchema
getSampleData
getTableSize
executeQuery
getDatabaseInfo
listRelationships
getIndexes
profileColumn
searchSchema
listProcedures
getTriggers
compareSchemas

可以将它们合并为:

db_tables
db_query
db_advanced

这些工具中的第一个可以依赖如下架构:

const tablesSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("list"),
    schema: schemaField,
  }),
  z.object({
    action: z.literal("schema"),
    tableName: z.string(),
    schema: schemaField,
  }),
  z.object({
    action: z.literal("sampleData"),
    tableName: z.string(),
    schema: schemaField,
    rowCount: z.number().optional().default(10),
  }),
  z.object({
    action: z.literal("size"),
    tableName: z.string(),
    schema: schemaField,
  }),
]);

从模型的角度来看,只存在一个工具:

db_tables

它由一组明确定义的可行操作作为支撑:

list
schema
sampleData
size

在底层,运行时仍然会将请求分发到四个不同的处理程序:

switch (args.action) {
  case "list":
    return handleListTables(args.schema);

  case "schema":
    return handleGetTableSchema(
      args.tableName,
      args.schema
    );
  case "sampleData":
    return handleGetSampleData(
      args.tableName,
      args.schema,
      args.rowCount
    );
  case "size":
    return handleGetTableSize(
      args.tableName,
      args.schema
    );
}

这种分离很重要:

合并操作发生在MCP接口层面,而非底层的业务逻辑中。

每个内部处理程序都具备独立的可测试性且易于维护。

5. Zod区分联合类型是理想选择

在TypeScript代码库中,z.discriminatedUnion()提供了一种简洁的方式来建模这种模式。

例如:

const querySchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("execute"),
    query: z.string(),
    params: z.record(z.string()).optional(),
    limit: z.number().optional().default(100),
  }),
  z.object({
    action: z.literal("info"),
  }),
]);

由此可以直接推导出该类型的结构:

type QueryInput = z.infer<typeof querySchema>;

这种方法能带来三个实际好处:

  1. 一个统一的MCP工具。
  2. 基于操作字段的明确路由机制。
  3. 运行时执行的严格验证。

该架构的整体结构如下:

MCP Tool
                │
                ▼
        Discriminated Union
                │
          ┌─────┴─────┐
          │  action   │
          └─────┬─────┘
                │
       ┌────────┼────────┐
       ▼        ▼        ▼
    Handler A Handler B Handler C
       │        │        │
       └────────┼────────┘
                ▼
             Backend

与数十个互不相关的顶层工具相比,模型要导航起来容易得多。

6. 工具数量不等于功能数量

这可以说是这种方法中最重要的理念。

假设最终出现的情况是:

3 tools
13 actions

这并不意味着有十个功能消失了。实际上你拥有的是:

3 external interfaces
13 internal capabilities

简而言之:

Tool count ≠ capability count

随着智能体系统规模的扩大,这种区分的重要性愈发凸显。大型组织的工具集可能如下所示:

Jira             20 operations
GitLab            25 operations
Confluence        15 operations
Sentry             8 operations
ELK               10 operations
Jaeger             6 operations
Database           20 operations

这很容易导致超过100次独立操作。将所有操作直接暴露给模型会使得工具界面变得过于臃肿。相反,你可以将其整合为类似以下形式:

jira
gitlab
confluence
sentry
observability
database

同时保持底层操作的总数不变。

7. 为何这能减少令牌使用量

其主要优势在于减少了模型需要解析的工具元数据量。不必列出:

Tool 1
Tool 2
Tool 3
Tool 4
...
Tool 30

而是呈现为:

Tool A
Tool B
Tool C

这样可以减少:

  • 工具描述
  • 重复的参数元数据
  • 重复的语义解释
  • 在顶层工具中选择的复杂性
  • 工具定义所占用的上下文空间

不过,这不应被视为必然能按比例节省资源。从:

30 tools → 3 tools

不会自动转化为:

90% lower token cost

因为合并后的模式本身可能会变得更大。更准确的表述方式是:

将多个操作整合到同一个工具中通常可以减少代理需要读取的元数据量,不过实际能节省多少令牌或响应时间取决于诸多因素,比如合并后模式的大小、字段描述的详细程度、模型提供方在处理工具定义时的特殊机制,以及你的代理框架管理工具调用的具体方式。

这一细微差别值得牢记。

8. 将所有内容整合在一起并不一定更好

这里有一个值得注意的权衡。

想象一个工具提供了40种不同的操作:

enterprise({
  action: ...
})

这些操作包括:

createCustomer
deleteCustomer
searchInvoice
rotateCredentials
deployService
createRepository
getTrace
searchLogs
...

从技术上来说这是可行的。

但这属于糟糕的设计。

模型现在不得不处理一个庞大的架构。

你并没有解决原始问题,只是将其从:

30 tools

转移到了:

1 giant tool

更好的方法是按领域进行整合。

例如:

customer
billing
repository
observability
deployment

目标应该是五种工具,而不仅仅是一种。

这引出了一个指导原则:

缩小工具的功能范围,但不要草率行事。

9. 架构同时作为智能体接口的一部分

当一个工具需要承担多种功能时,action参数就会承载过重的意义。

一个较弱的版本:

action: z.string()

一种改进方案:

action: z.enum([
  "search",
  "get",
  "create",
  "update"
])

当每个操作都需要不同参数时,这是最理想的选择:

z.discriminatedUnion("action", [
  searchSchema,
  getSchema,
  createSchema,
  updateSchema,
])

在这里,选定的操作及其所需参数通过真正的类型安全机制紧密关联。

例如:

action = "search"
→ query required
action = "get"
→ issueId requiredaction = "create"
→ title + description required

这比为每种可能的操作都准备带有可选字段的单一松散类型架构要精确得多。

10. MCP下的JSON Schema复杂性

实际应用中还存在额外的问题。

MCP工具的架构最终会被编译为JSON Schema格式。

根据所使用的转换器不同,Zod区分联合类型可能会生成:

{
  "anyOf": [...]
}

或者:

{
  "oneOf": [...]
}

MCP架构流程的某些部分假设架构的根节点是一个普通对象。

但这一假设并不总是成立。

在至少一种实际实现中,MCP SDK 内部的 Zod 兼容层是基于对象结构构建的,无法正确处理区分联合类型。

解决方案是为生成的架构添加标准化步骤。

从概念上来看,其流程如下:

Zod discriminated union
          │
          ▼
     JSON Schema
          │
          ▼
   MCP normalization
          │
          ▼
MCP-compatible object schema

由此可得的架构启示是:

在不影响实际验证器严格性的前提下,重新设计展示给模型的架构结构。

原始的 Zod 架构应始终作为验证的唯一权威依据。

11. 两种不同的架构:一种用于模型,一种用于运行时

这表明有一个更重要的区别需要明确说明。

这里实际上存在两项独立的任务:

LLM-facing representation
        │
        ▼
Tool selection and argument generation
Runtime representation
        │
        ▼
Validation and execution

展示给大语言模型的架构应具备:

  • 易于阅读
  • 简洁
  • 清晰明确
  • 易于选择
  • 运行时使用的模式应具备:

    • 严格性
    • 安全性
    • 可预测性
    • 最终确定性

    这两种表示形式无需完全一致。扁平化、简化的对象结构可能有助于模型更轻松地选择正确操作,而底层的区分联合类型仍会在后台进行验证。

    这可归纳为代理系统的一条更普遍的规则:

    根据模型读取方式调整模式,但绝不能将面向模型的模式作为唯一的防御手段。

    12. 工具合并后描述更具重要性

    当有30个独立工具时,每个工具都可以采用狭义、具体的描述方式。

    当有3种工具时,描述必须涵盖所有可能的操作范围。

    例如:

    Unified tool for advanced database analysis.
    
    ACTIONS:
    relationships
      List foreign key relationships.
    indexes
      Get indexes for a table.
    profileColumn
      Analyze nullability, cardinality and top values.
    searchSchema
      Search tables and columns.
    listProcedures
      List stored procedures.
    triggers
      Get triggers.
    compareSchemas
      Compare two table definitions.
    

    这不仅仅是给人看的文档。

    它还是模型处理请求的路径机制的一部分。

    一种有助于理解的方式是:

    Tool name
        +
    Description
        +
    Action enum
        +
    Parameter schema
            ↓
    Agent routing signal
    

    因此,整合工具应当促使你写出更详尽的描述,而非减少描述数量。

    13. 这种模式不仅限于数据库

    将这种方法应用到更广泛的工程系统中,效果会更加显著。

    Jira

    与其直接展示:

    searchIssues
    getIssue
    createIssue
    updateIssue
    addComment
    transitionIssue
    assignIssue
    getTransitions
    

    展示:

    jira_issues
    

    带有如下操作集:

    search
    get
    create
    update
    comment
    transition
    assign
    

    GitLab

    与其分别为仓库、问题、合并请求和流水线提供独立的工具:

    gitlab_repository
    gitlab_issues
    gitlab_mergeRequests
    gitlab_pipelines
    

    可以将它们整合为单个入口点,每个入口点都有其独立的操作集。

    Confluence / Wiki

    无需将各种操作分散在多个工具中:

    searchPages
    getPage
    createPage
    updatePage
    deletePage
    getChildren
    

    可将它们合并为一个:

    wiki
    

    由如下操作列表驱动:

    search
    get
    create
    update
    children
    

    Sentry

    这里也采用类似的结构:

    sentry
    

    其支撑机制为:

    searchIssues
    getIssue
    events
    releases
    projects
    

    ELK

    日志搜索也适用同样的思路:

    logs
    

    具体表现为:

    search
    aggregate
    fields
    indices
    

    Jaeger

    对于分布式追踪而言:

    tracing
    

    具体表现为:

    search
    getTrace
    services
    operations
    

    这些示例均不依赖于所选择的特定名称,重要的是底层的架构设计思路,而非每个操作所使用的具体标签。

    14. 可以直接使用现有的MCP服务器,无需重新构建

    这种设计较为实际的好处之一是,你几乎无需从头重新实现集成功能。许多常见平台都已存在现成的MCP服务器。不过问题在于,这些服务器的默认工具界面往往并未针对特定智能体的需求进行优化。

    无需将智能体直接连接到庞大的工具集合中:

    Agent
      │
      ├── 20 Jira tools
      ├── 20 GitLab tools
      └── 15 Confluence tools
    

    你可以在其间插入一个专门设计的轻量级层:

    Agent
                       │
                 Custom MCP Layer
                       │
            ┌──────────┼──────────┐
            ▼          ▼          ▼
          Jira       GitLab    Confluence
            │          │          │
         existing   existing   existing
           APIs       MCP        MCP
    

    该自定义层充当面向智能体的接口层。当底层集成在功能上完好无损,但提供的操作细节过于复杂,超出了智能体直接处理的范畴时,这一层尤为重要。

    15. 将MCP视为适配层

    这引出了一个更通用的架构理念。传统的配置方式如下:

    Agent → MCP → Service
    

    自定义的设置看起来像这样:

    Agent
      ↓
    Agent-oriented MCP facade
      ↓
    Service-specific MCP/API
      ↓
    Service
    

    中间的接口层可以承担以下职责:

    • 整合各种工具
    • 执行权限检查
    • 规范输入数据
    • 规范输出数据
    • 记录日志
    • 追踪操作流程
    • 实现缓存功能
    • 限制请求频率
    • 应用特定于组织的策略规则

    从这种视角来看,MCP层不再仅仅是传输机制,而变成了真正的代理程序的接口层。

    16. 访问控制仍应位于操作层面

    有一个安全方面的问题值得注意。如果将以下内容:

    jira_create
    jira_update
    jira_delete
    

    合并为单一的调用形式,如下所示:

    jira({
      action: ...
    })
    

    则不能简单地授予通用的权限,例如:

    permission: jira
    

    并且假设所有底层操作的风险都是相同的。权限检查需要在单个操作的层面进行,例如:

    jira.search       → allowed
    jira.get          → allowed
    jira.create       → allowed
    jira.update       → approval required
    jira.delete       → denied
    

    该整合工具仅仅是接口层面的简化——它绝不能成为一种默许超出预期权限的快捷方式。

    17. 日志需记录操作内容,而不仅仅是工具名称

    一旦工具被合并,仅在日志中记录工具名称就不再具有实用价值。一个不完善的日志条目如下所示:

    tool = jira
    

    更好的日志会包含操作内容:

    {
      "tool": "jira",
      "action": "search",
      "duration_ms": 142
    }
    

    更为详细的日志还会添加更多的执行信息:

    {
      "tool": "jira",
      "action": "search",
      "status": "success",
      "duration_ms": 142,
      "request_id": "...",
      "agent_id": "...",
      "user_id": "..."
    }
    

    现在的实际执行单元是操作与工具的组合:

    tool + action
    

    而非单独的工具:

    tool
    

    在试图调试智能体实际执行了什么操作时,这种区分非常重要。

    18. 将底层处理函数分开

    整合工作应在智能体可访问的边界处进行,而非在实现代码内部。避免将所有功能合并为一个庞大的处理函数:

    async function jira(args) {
      // everything
    }
    

    相反,应将其分发到不同的函数中:

    async function jira(args: JiraInput) {
      switch (args.action) {
        case "search":
          return searchIssues(args);
        case "get":
          return getIssue(args);
        case "create":
          return createIssue(args);
        case "update":
          return updateIssue(args);
      }
    }
    

    每个处理函数都可以独立进行测试:

    jira()
      │
      ├── searchIssues()
      ├── getIssue()
      ├── createIssue()
      └── updateIssue()
    

    即便外部工具的数量减少,这种方式也能保持代码库的可维护性。

    19. 决定哪些内容需要合并的实用规则

    判断两个操作是否属于同一工具的一个良好准则是:

    工程师是否会自然而然地将这些操作归类到同一功能下?

    例如,这些操作显然属于同一类:

    Jira search
    Jira get issue
    Jira update issue
    

    但这些却不行:

    Jira update issue
    Elasticsearch search logs
    Create AWS infrastructure
    

    另一个有用的检查标准:

    在决定使用这些操作时,通常需要相同的上下文信息吗?

    如果答案是肯定的,那么将它们合并通常会更有好处。否则,最好仍将它们作为独立的工具使用。

    20. 何时不应合并工具

    有时将工具分开使用才是正确的选择。

    无关的领域

    database
    payments
    infrastructure
    email
    

    不应被简化为类似这样的形式:

    enterprise({
      action: ...
    })
    

    仅仅因为可以,就将无关的领域混入同一个调度器中,并不会让接口更加清晰有序。

    过大的操作集

    如果一个整合后的工具需要处理五十种不同的操作,那么生成的架构可能会变得臃肿,模型也难以理解,从而违背了整合的初衷。

    具有更高权限的操作

    诸如以下操作:

    deleteProductionData
    rotateCredentials
    deployProduction
    

    即便从技术上讲它们属于与风险较低的操作相同的领域,也可能需要独立的权限范围。

    具有不同生命周期保障的操作

    只读调用和破坏性调用有时作为独立的接口使用效果更好:

    jira_read
    jira_write
    

    而不是被整合到一个过大的工具中。

    与之前一样,没有固定的规则——这取决于具体系统。

    21. 减少工具数量并非真正目标

    目标并不是将所有内容都简化为:

    30 → 1
    

    真正的目标是:

    30 → smallest useful semantic surface
    

    在某个系统中,正确的数值可能如下所示:

    30 → 3
    

    在另一个系统中则是:

    30 → 7
    

    而在再另一个系统中则是:

    30 → 12
    

    你实际上要优化的是:

    minimize unnecessary tool metadata
    

    同时不牺牲:

    semantic clarity
    validation
    security
    maintainability
    

    22. 可复用的架构结构

    可扩展的架构通常遵循这种结构:

    Agent
                              │
                              ▼
                    ┌──────────────────┐
                    │  Agent MCP Layer │
                    └────────┬─────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
            jira           gitlab          wiki
              │              │              │
         ┌────┼────┐    ┌────┼────┐    ┌────┼────┐
         ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼
      search get create issues MR pipeline search get update
              │              │              │
              └──────────────┼──────────────┘
                             ▼
                     Existing APIs/MCPs
    

    模型与简洁的接口进行交互,而在其下方,集成层仍能访问所有操作功能。

    23. 重新思考MCP的用途

    关于MCP的一种常见认知是:

    “将每个API调用都转化为独立的工具。”

    这确实是一个不错的起点。

    但对于实际运行的系统而言,更有意义的问题是:

    模型究竟应该看到什么?

    这两种框架方式并不总会导致相同的设计结果。

    以一个公开 REST API 为例:

    GET /issues
    GET /issues/:id
    POST /issues
    PATCH /issues/:id
    POST /issues/:id/comments
    POST /issues/:id/transition
    

    没有必要将每个 HTTP 路由都对应为一个独立的面向模型的工具。MCP 层可以充当这些端点的语义封装层——就像根据特定使用者的需求来设计 API,而非直接暴露底层服务或数据库结构。

    24. 将工具设计视为智能体用户体验问题

    传统的 API 设计会询问:

    存在哪些端点?

    而针对智能体进行设计则会提出不同的问题:

    模型实际上试图做出什么决策?

    负责集成 API 的人类工程师可能会采用以下方式:

    getIssue
    getIssueComments
    getIssueTransitions
    getIssueWorklogs
    

    因为每次调用都能清晰地对应到一项操作。

    另一方面,代理可能与以下元素更好地配合使用:

    jira_issue({
      action: "get",
      ...
    })
    

    与以下内容配对使用:

    jira_issue({
      action: "comments",
      ...
    })
    

    因为它首先选择域名,然后再缩小到具体的操作——这是一个两步的路由过程:

    Domain
      ↓
    Action
      ↓
    Arguments
    

    而不是强制模型去解决:

    One large global tool-selection problem
    

    25. 可重复使用的最小化模式

    这种方法可以很自然地转化为 TypeScript:

    const toolSchema = z.discriminatedUnion("action", [
      searchSchema,
      getSchema,
      createSchema,
      updateSchema,
    ]);
    
    server.registerTool(
      "jira",
      {
        description: "...",
        inputSchema: toolSchema,
      },
      async (args) => {
        switch (args.action) {
          case "search":
            return search(args);
          case "get":
            return get(args);
          case "create":
            return create(args);
          case "update":
            return update(args);
        }
      }
    );
    

    代码本身相当简单。真正的设计工作在于回答以下问题:

    1. 哪些操作在逻辑上属于同一组?
    2. 操作应遵循怎样的命名规则?
    3. 工具描述中应包含哪些内容?
    4. 每个操作需要哪些必填参数?
    5. 哪些操作需要额外的权限检查?
    6. 响应应如何保持一致的形式?
    7. 每个操作应如何进行日志记录和追踪?

    26. 用真实数据追踪影响

    整合工具不应仅凭信念行事——需要用数据来验证。

    在做出变更之前,你可以记录如下内容:

    Tools: 32
    Tool schema tokens: X
    Average tool-selection latency: Y
    Tool-selection errors: Z
    

    在采用多路复用设计后,再次收集相同的指标:

    Tools: 5
    Tool schema tokens: X'
    Average tool-selection latency: Y'
    Tool-selection errors: Z'
    

    值得追踪的最有参考价值的信号包括:

    • 暴露出的工具数量
    • 模式的总大小
    • 提示词或上下文消耗的令牌数
    • 智能体选择正确工具的准确率
    • 发出无效工具调用的频率
    • 参数验证失败的频率
    • 响应延迟
    • 实际成功执行的频率
    • 智能体是否完成了整体任务

    目标并非仅仅追求:

    fewer tools
    

    而是为了优化:

    better agent performance per unit of context
    

    27. 面向智能体的 API 的更通用模式

    这一理念的应用范围远超 MCP 本身。

    你会发现相同的原则也适用于:

    • 函数调用
    • OpenAI 风格的工具定义
    • SDK
    • 自定义智能体运行时
    • 工作流引擎
    • 插件架构
    • REST 到智能体的适配器
    • GraphQL 到智能体的桥接器

    其核心模式如下:

    Traditional API
           ↓
    Many low-level operations
           ↓
    Agent-oriented facade
           ↓
    Small semantic tool surface
           ↓
    Action routing
           ↓
    Strict execution layer
    

    这并非针对数据库的特定技巧。

    这是一种用于设计智能体可使用的接口的通用模式。

    28. 核心原理

    需要牢记的思维模型很简单:

    ┌──────────────────────┐
                     │      30 APIs         │
                     │    capabilities      │
                     └──────────┬───────────┘
                                │
                         Consolidation
                                │
                                ▼
                     ┌──────────────────────┐
                     │      3–5 tools       │
                     │  semantic domains    │
                     └──────────┬───────────┘
                                │
                         Action routing
                                │
                                ▼
                     ┌──────────────────────┐
                     │    30 handlers       │
                     │  original abilities │
                     └──────────────────────┘
    

    有意义的转变并非:

    30 capabilities → 3 capabilities
    

    而是:

    30 exposed tools → 3 exposed interfaces
    

    系统功能本身没有任何变化。

    变化在于模型需要直接处理的表面积大小。

    总结

    随着智能体系统被集成到更多的基础设施中,它们可使用的工具数量只会持续增加。

    Jira、GitLab、Confluence、Sentry、ELK、Jaeger、各类数据库、云服务提供商以及内部系统等,每一种都能提供数十甚至数百种独立的操作功能。

    将所有这些操作都作为单独的工具交给模型处理,往往并非合适的架构设计。

    更有效的策略是在这些系统之上构建一个面向智能体的工具层:

    Many low-level operations
              ↓
    Semantic domain grouping
              ↓
    Discriminated action schemas
              ↓
    Small MCP tool surface
              ↓
    Strict runtime validation
              ↓
    Original capabilities
    

    核心原则是:

    应减少模型需要处理的工具数量——而非削减系统实际提供的功能数量。

    与三十种底层操作集成,并不意味着模型必须看到三十个独立的工具。

    或许只需要三个经过精心设计的接口,每个接口都拥有明确界定的操作范围即可。

    一旦你不再将现有的MCP服务器视为固定不变、不可更改的契约,而是将其视为可以封装、重塑和调整的构建模块,以匹配你的智能体实际需求,这种方法的价值就会更加凸显。

    未来,优秀的MCP设计重点或许不再是展示API支持的所有操作,而是打造智能体完成任务所需的最小且最实用的功能接口。