Ctrl K
ComunidadGuías…

docs/zh/mcp-tools/discovery.mdx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
title: "MCP 工具:发现与类型化解析"
sidebarTitle: "发现与类型化解析"
description: "如何低成本发现 World Monitor MCP 工具:describe_tool、outputSchema 类型化解析与面向语音智能体的 spoken 模式。适合需要节省上下文的智能体:先用 describe_tool 按需读取单个工具定义,再依据 outputSchema 直接解析结构化结果,无需先发起示例调用。"
---

本页属于 [MCP 工具参考](/zh/mcp-tools-reference),该索引列出全部工具。连接、认证、套餐与错误见 [MCP Server 概览](/zh/mcp-overview)。

在深入逐工具参考之前,两种机制使发现工具的成本低于从头到尾阅读本页。如果您从 REST 路由而非工具名称开始,请使用 [API 覆盖表](/zh/mcp-overview#api-覆盖);逐工具的 **API 端点** 行表示确切的 `_apiPaths` 声明,而 `none directly` 表示该工具返回数据但不声明等价的 REST 路由。

MCP 覆盖是有意精选的。某些 OpenAPI 操作仅限 REST,因为它们会修改状态、传递 LLM 成本、在缓存未命中时获取付费/高基数上游,或需要手动缓存键映射。[API 覆盖部分](/zh/mcp-overview#api-覆盖) 列出了强制类别并链接了当前的跟进追踪。

### `describe_tool` — 按需获取单个工具的定义

自 v1.34.0 起,`tools/list` 返回每个工具的完整 `description`(每条已注册描述都在 1,024 字节的逐工具预算内,由 CI 强制执行),并附带其 `inputSchema` 与 `outputSchema`(v1.6.0),使模型仅凭列表即可选择工具并编写 JMESPath 投影。`describe_tool` 返回同一工具的相同定义,用于在不重新列出整个目录的情况下重读单个条目:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": { "name": "describe_tool", "arguments": { "tool_name": "get_chokepoint_status" } }
}
```

响应 — 与该工具的 `tools/list` 条目完全相同:同样的 `description` 和同样的 `inputSchema.properties` 文本:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"name\":\"get_chokepoint_status\",\"description\":\"Live maritime chokepoint status: per-chokepoint vessel transit counts (10-min cadence), rolling transit summaries, a port-country coverage index, plus static reference data and flow aggregates. Covers Suez, Hormuz, Malacca, Bab-el-Mandeb, Panama, etc.\",\"inputSchema\":{ /* full properties with full descriptions */ },\"outputSchema\":{ /* … */ },\"annotations\":{\"readOnlyHint\":true,\"destructiveHint\":false,\"idempotentHint\":true,\"openWorldHint\":false},\"_meta\":{ /* … */ }}"
      }
    ],
    "structuredContent": { "name": "get_chokepoint_status", "description": "…", "inputSchema": {}, "outputSchema": {}, "annotations": {}, "_meta": {} }
  }
}
```

`describe_tool` **豁免 Pro 每日配额**(每分钟速率限制仍然适用)。此豁免是有意为之 — 将元数据查询计入 50/天 的上限会阻碍探索。两种常见工作流:

- **会话中途重读单个工具。** 调用 `describe_tool` 把单个工具的定义重新放回上下文,而无需再次为整个 `tools/list` 目录付出代价。
- **针对不熟悉的响应首次编写 JMESPath。** 调用 `describe_tool` 读取 `outputSchema`(见下一节),无需为真正的 `tools/call` 消耗配额槽位。

`describe_tool` 在正常的 `content[0].text` 内返回两种软错误信封:

- `{ "error": "missing_tool_name", "hint": "Pass tool_name as a non-empty string matching a tool from tools/list." }` — `tool_name` 被省略、为空或非字符串。
- `{ "error": "unknown_tool", "requested": "<the bad name>", "available": [...sorted list of all tool names...] }` — `tool_name` 不匹配。`available` 数组让 LLM 能在额外一次调用中自我纠正。

`describe_tool` 的完整逐工具参考(参数、响应形状、配额策略)位于 Meta 部分的 [`describe_tool`](/zh/mcp-tools/meta#describe_tool)。

### `outputSchema` — 无需示例调用的类型化解析

自 v1.6.0 起,每个工具的 `tools/list` 条目都声明了规范定义的 [MCP 2025-06-18 `Tool.outputSchema`](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#tool-result-schema)。该 schema 描述 `result.structuredContent`;其第一个 `anyOf` 分支是工具的文档化负载形状,也就是普通调用时 `result.content[0].text` 所序列化的文档(投影或 `summary: true` 会重塑文本,并在 `structuredContent` 中被包装;见下表) — 让客户端无需发起真正的 `tools/call` 即可编写投影、验证响应或生成类型。

Schemas 在每次 `tools/list` 时**无条件**输出,无论协商的 `protocolVersion` 如何。处于较早 `2025-03-26` 基线的客户端仍会收到它们,并且(根据规范)应忽略未知字段而不是报错。

**`structuredContent` 与所发布 schema 的形状(v1.21.0)。** 每次成功的 `tools/call` 都会把载荷返回两次:`result.content[0].text` 中的 JSON 文本,以及 `result.structuredContent` 中的对象。严格的 MCP 客户端要求如此:一旦工具发布了 `outputSchema`,官方 SDK 会拒绝不带 `structuredContent` 的结果(`-32600`),并用该 schema 校验收到的内容(`-32602`)。有四类响应不是工具的文档化形状,因此所发布的 schema 把它们一并列出:

```json
{
  "type": "object",
  "anyOf": [
    { "…": "工具的文档化形状 —— 即下方展示的 schema" },
    { "required": ["projection"] },
    { "required": ["data", "_attribution"] },
    { "required": ["_budget_exceeded"] },
    { "required": ["_jmespath_error"] }
  ]
}
```

| 响应 | `content[0].text` | `structuredContent` |
|---|---|---|
| 普通调用 | 载荷 | 同一载荷 |
| `jmespath` 投影,或缓存工具的 `summary: true` | 重塑后的值(任意 JSON 值) | `{ "projection": <该值> }`,因为该字段必须是对象 |
| 带许可要求的工具上的投影 | `{ "data": …, "_attribution": … }` | 同一对象 |
| `_budget_exceeded` 或 `_jmespath_error` | 信封 | 同一信封 |

文本与早期版本返回的内容逐字节一致,因此只读取 `content[0].text` 的客户端不会看到任何变化。要以编程方式读取工具的文档化形状,请取 `outputSchema.anyOf[0]`。

示例 — `get_country_risk` 的 `outputSchema` 文档化形状(即其 `anyOf[0]`):

```json
{
  "type": "object",
  "properties": {
    "countryCode": { "type": "string" },
    "countryName": { "type": "string" },
    "ciiHistory": { "type": "object", "description": "Optional UTC daily history, added only with history_days." },
    "cii": {
      "type": ["object", "null"],
      "description": "Absent when the country is not tracked, and always absent when upstreamUnavailable is true.",
      "properties": {
        "combinedScore": { "type": "number", "description": "The headline CII, 0-100." },
        "trend": { "type": "string", "enum": ["TREND_DIRECTION_UNSPECIFIED", "TREND_DIRECTION_RISING", "TREND_DIRECTION_STABLE", "TREND_DIRECTION_FALLING"] },
        "components": {
          "type": "object",
          "description": "Names are historical and do NOT describe their contents -- read the descriptions.",
          "properties": {
            "ciiContribution":  { "type": "number", "description": "DOMESTIC UNREST contribution." },
            "geoConvergence":   { "type": "number", "description": "ARMED CONFLICT contribution." },
            "militaryActivity": { "type": "number", "description": "SECURITY AND MOBILITY contribution." },
            "newsActivity":     { "type": "number", "description": "INFORMATION ENVIRONMENT contribution." }
          }
        }
      }
    },
    "advisoryLevel":   { "type": "string" },
    "sanctionsActive": { "type": "boolean" },
    "sanctionsCount":  { "type": "number" },
    "fetchedAt":       { "type": "number", "description": "Unix epoch ms; 0 means unknown." },
    "upstreamUnavailable": { "type": "boolean", "description": "True when ANY required upstream read failed; the whole response is withheld, so the zeroed risk fields mean UNKNOWN, not low." }
  }
}
```

缓存工具将其声明的 `data` 形状包装在标准的新鲜度信封中:

```json
{
  "type": "object",
  "required": ["cached_at", "stale", "data"],
  "properties": {
    "cached_at": { "type": ["string", "null"], "description": "ISO-8601 timestamp of the OLDEST contributing cache key." },
    "stale":     { "type": "boolean", "description": "True when any contributing cache key fails its freshness contract: fetched longer ago than its per-key maxStaleMin budget, below a declared minRecordCount, or — for keys that declare a content-age contract — carrying upstream observations older than maxContentAgeMin even though the fetch itself is recent. A recent cached_at with stale:true means the fetch is current but the underlying data has stopped advancing, so refetching will not help." },
    "data":      { "type": "object", "properties": { /* per-tool fields */ } }
  }
}
```

使用 schema 作为编译时提示的 TypeScript 类型化解析草图:

```ts
// Synthesise types from the schema once (e.g. with json-schema-to-typescript).
type CountryRisk = {
  countryCode: string;
  countryName: string;
  ciiHistory?: {
    methodologyVersion: string; requestedDays: number; returnedDays: number; stale: boolean;
    points: Array<{ date: string; combinedScore: number; components: { ciiContribution: number; geoConvergence: number; militaryActivity: number; newsActivity: number } }>;
    change: { fromDate: string; fromScore: number; toDate: string; toScore: number; delta: number } | null;
    versionBreaks: Array<{ date: string; from: string; to: string }>;
  };
  // The four component names are historical misnomers: ciiContribution is
  // domestic unrest, geoConvergence is armed conflict, militaryActivity is
  // security/mobility, newsActivity is the information environment.
  cii?: {
    combinedScore: number;
    trend: 'TREND_DIRECTION_UNSPECIFIED' | 'TREND_DIRECTION_RISING' | 'TREND_DIRECTION_STABLE' | 'TREND_DIRECTION_FALLING';
    components?: { ciiContribution: number; geoConvergence: number; militaryActivity: number; newsActivity: number };
  };
  advisoryLevel: string;
  sanctionsActive: boolean;
  sanctionsCount: number;
  fetchedAt: number;
  // Check this BEFORE reading any score: true means a required upstream
  // read failed and the response was withheld — the zeroed fields are
  // unknown, not calm.
  upstreamUnavailable: boolean;
};

const reply = await callTool('get_country_risk', { country_code: 'IR' });
const text = reply.result?.content?.[0]?.text;
if (typeof text !== 'string') throw new Error('no text payload');

type SoftEnvelope =
  | { _budget_exceeded: true; budget_bytes: number; actual_bytes: number; hint: string }
  | { _jmespath_error: string; original_keys: string[] };

const parsed = JSON.parse(text) as CountryRisk | SoftEnvelope;
// Check for BOTH soft-envelope discriminators BEFORE consuming sibling fields as data.
// A `_jmespath_error` payload that falls through to the success branch would silently
// dereference undefined — the exact anti-pattern the catalog warns against.
if ('_budget_exceeded' in parsed) {
  // narrow with jmespath / filters and retry — see /mcp-error-catalog
} else if ('_jmespath_error' in parsed) {
  // fix the projection using `original_keys` as the schema hint — see /mcp-error-catalog
} else {
  console.log(parsed.cii?.combinedScore, parsed.cii?.components?.geoConvergence);
}
```

须知事项:

- 每个 schema 的 `additionalProperties` 都隐式保留(= true),因此生产者侧的前向兼容新增不会突然导致验证失败。
- 每个数组的 `items.properties` 列出已知字段,但不会枚举每个观察到的键 — schema 是 JMESPath 编写的**提示界面**,而非字节码级别的契约。
- 文档化形状(`anyOf[0]`)仅描述成功路径的负载。两种目录级软错误信封(`_budget_exceeded`、`_jmespath_error`)会完全替换负载;自 v1.21.0 起,它们与投影一样,在所发布的 `anyOf` 中各有自己的分支,因此严格的客户端会接受它们。两种信封请参见 [MCP 错误目录](/zh/mcp-error-catalog)。

通用参数:

- 每个工具都接受 `jmespath`(string),一个在逐工具筛选和 `summary` 之后应用的可选服务端投影。
- 每个缓存工具还接受 `summary`(boolean),它返回计数加 3 项样本,而非完整列表。
- 下方的逐工具表仅列出注册表声明的工具特定参数;通用注入参数有意在此仅记录一次。