创建日期:2026-09-08 | 最近更新:2026-09-08 事实核对基于 zod 4.5.4 与
@modelcontextprotocol/sdk1.30.0(2026-07-27)在本机的实测与类型声明核对。
MCP 为什么“绕不开” Zod:把协议要的 JSON Schema 藏进作者体验
一句话:流传的「MCP 一定要用 zod」要对半看——协议本身只认 JSON Schema(语言无关),并不强制 zod;但 TypeScript 官方 SDK 把 zod 做成了工具参数的“作者语言”,于是你在 B 站/掘金看到的每个 MCP 教程里,工具参数都是
z.string()/z.number()。这一篇拆清楚:哪层用 zod、哪层不用、为什么偏偏是 zod,以及从 v3 迁 v4 要注意什么。
先接上一篇:Zod 入门 与本站的 MCP 栏目(协议本身怎么定义 tool)。
1. 协议层:只认 JSON Schema,不认 zod
MCP 里一个工具长这样(协议是语言无关的 JSON-RPC 风格):tools/list 时服务端返回每个工具的 inputSchema,tools/call 时客户端把 { name, arguments } 发给服务端。关键字段 inputSchema 的规范类型是 JSON Schema:
{
"name": "web_search",
"description": "搜索网页",
"inputSchema": {
"type": "object",
"properties": {
"keyword": { "type": "string", "description": "搜索关键词" },
"limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
},
"required": ["keyword"]
}
}
所以严格来说,任何语言的 MCP 实现都不需要 zod——Python SDK 用的是自己的 pydantic/schema 思路,你只需要输出这份 JSON Schema。zod 不是协议的入场券。
2. 那 TS SDK 为什么绑 zod 绑得这么死
打开 @modelcontextprotocol/sdk 的 package.json(1.30.0),依赖里赫然躺着 zod(^3.25 || ^4.0)和 zod-to-json-schema。翻开它的类型声明,服务端注册工具时接受的就是一个 zod raw shape(内部叫 ZodRawShapeCompat,即 Record<string, zod schema>),并且会从 shape 里推断回调参数类型(ShapeOutput<Args>):
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const server = new McpServer({ name: 'search', version: '1.0.0' });
server.registerTool(
'web_search',
{
title: '网页搜索',
description: '搜索网页,可按需返回条数',
inputSchema: { // 传的就是 zod shape
keyword: z.string().describe('搜索关键词'),
limit: z.number().int().min(1).max(50).default(10).describe('返回条数上限'),
},
},
async ({ keyword, limit }) => { // keyword: string, limit: number —— 类型自动推断
// limit 已经被 zod 的 default 补齐过
return { content: [{ type: 'text', text: `${keyword} x${limit}` }] };
},
);
(老版本的 server.tool(name, description, paramsShape, cb) 写法已在 1.30 标记 deprecated,新 API 用 registerTool,但 schema 还是 zod。)
一个 zod 对象,在 SDK 里被用在了三个不同的地方,这正是「绕不开」的根本原因:
| 环节 | zod 干了什么 | 换成别的东西要自己补什么 |
|---|---|---|
| 作者体验(开发时) | zod raw shape 直接当参数表,且自动推断 handler 形参类型 | 手写 JSON Schema 没类型;手写类型没法进运行时 |
| 上行(tools/list) | 把 shape 转成协议要的 inputSchema(JSON Schema)发给客户端/给模型看 | 需要自己写 zod→JSON Schema 转换 |
| 下行(tools/call) | 收到 arguments 后 safeParse:校验、补 default、再交给你的回调 | 需要自己写一套运行时校验 |
同一个 schema 描述了三件事——“一份声明,类型、协议、校验全齐”——这正是 Zod 入门 里那个心智模型的翻版,只是这次消费方是 MCP SDK。
3. 为什么偏偏是 zod(而不是 TypeBox / valibot / io-ts)
MCP 诞生在 2024 年底,TS 的 agent/AI 生态当时已经把 zod 当成了事实标准:tRPC、React Hook Form、AI SDK 全在它身上。官方 SDK 选 zod,等于选了「生态里开发者最熟、类型推导最顺、且有 zod-to-json-schema 这种成熟桥梁」的默认项——对教程和工具链的传染力是最重要的:官方示例用 zod,社区包(fastmcp 等)也跟着用 zod,最后所有 TS 教程都长成 z.string()。
对比几个邻居(帮你选型,不是分高下):
| 库 | 特点 | 在「MCP/agent 工具参数」语境 |
|---|---|---|
| Zod | TS 类型推断最强、生态最大、教程最多 | 默认选择;官方 SDK 一等公民 |
| TypeBox | schema 本身可 JSON 序列化、天生贴近 JSON Schema | 如果 schema 要跨语言/直接喂协议,参考本站 pi-ai 用它当工具 schema |
| valibot | 极小、零依赖、v3 也主打安全 | 体积敏感时替换 zod,但生态和 SDK 默认不如 zod |
一句话:MCP 需要 JSON Schema,zod 只是「最顺手的产出 JSON Schema 的方式之一」;之所以“看起来必须”,是官方 SDK 的默认 + 生态传染叠加出来的。
4. 实测:shape → inputSchema → safeParse
下面这段在 zod 4.5.4 上真实跑过——展示 SDK 内部那三个环节到底产出了什么:
import { z } from 'zod';
// ① registerTool 里那个 shape
const searchShape = z.object({
keyword: z.string().describe('搜索关键词'),
limit: z.number().int().min(1).max(50).default(10).describe('返回条数上限'),
});
// ② 上行:转成 tools/list 要发的 inputSchema
console.log(JSON.stringify(searchShape.toJSONSchema(), null, 2));
// ③ 下行:收到 arguments 后校验 + 补 default
const good = searchShape.safeParse({ keyword: 'mcp' }); // limit 缺省
const bad = searchShape.safeParse({ keyword: 'mcp', limit: 999 }); // 越界
实测输出:
② inputSchema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"keyword": { "type": "string", "description": "搜索关键词" },
"limit": { "default": 10, "type": "integer", "minimum": 1, "maximum": 50, "description": "返回条数上限" }
},
"required": ["keyword", "limit"],
"additionalProperties": false
}
③ 缺省参数 → {"keyword":"mcp","limit":10}
越界参数 → 被拒:✖ Too big: expected number to be <=50
两个值得注意的细节:
.describe('…')会被原样写进 JSON Schema 的description——也就是最终会发给 LLM 看。所以给工具参数写zod .describe()等于在写“给模型的工具说明书”,别只在代码注释里写一遍就完事。- zod v4 转出的对象默认
additionalProperties: false(JSON Schema 层面更严格);而zod.object().parse()默认是剥掉未知键而不是报错。两层严格度不一致,实践中靠把每个可能参数都显式声明(.optional()/.default())来避免歧义,别赌客户端一定只发 shape 里的键。
5. 从 v3 到 v4,这里最容易踩的坑
- SDK 同时兼容 zod 3 和 4:依赖写的是
^3.25 || ^4.0,类型层有zod/v3与zod/v4/core两套适配。你的项目跟 SDK 装到的 zod 主版本要一致,否则两个z副本会出现“shape 类型不认”的怪错。 - 转换库换代:老教程/老 SDK 依赖
zod-to-json-schema,而它从 2025-11 起停止维护,官方建议直接用 Zod v4 原生z.toJSONSchema()(实测输出 draft 2020-12)。SDK 1.30 仍带着旧依赖做兼容,但你在自己代码里转 schema 时,优先用 zod v4 原生 API。 z.enum/ 顶层字符串格式:与 Zod v3→v4 速览(§10)一致——v4 推荐z.email()、z.uuid()等顶层写法,老式z.string().email()也仍可用。
6. 那到底“什么时候必须用 zod”
只有一种情况称得上“必须”:
你用 TypeScript 官方 SDK 写 MCP server,且想要类型推断 + 自动转 JSON Schema + 自动校验的完整作者体验时——因为 SDK 把 zod 当成了唯一的一等 schema 语言,绕开它(手写 JSON Schema)等于放弃它替你做的类型与转换,还得自己跟 SDK 内部的 schema 语义对齐。
反过来,如果你写的是 Python/Go 客户端、或只有一份现成 JSON Schema、或在意跨语言 schema 复用,zod 都不是必需品——那是协议层“只要 JSON Schema”的功劳,也是它被设计成语言无关的原因。
关联
- 上一篇:Zod 入门(schema 心智模型、v3→v4 变化)
- 协议本身:MCP 入门
- 同生态对比:pi-ai 用 TypeBox 当工具 schema
参考
@modelcontextprotocol/sdk1.30.0:类型声明(server/zod-compat.d.ts、server/mcp.d.ts)与依赖(zod ^3.25 || ^4.0、zod-to-json-schema ^3.25.1)为本机核对- MCP 协议 Tool 规范:modelcontextprotocol.io/specification/2025-06-18/server/tools
- TS SDK 源码:github.com/modelcontextprotocol/typescript-sdk
- zod v4 原生 JSON Schema:zod.dev/json-schema
- zod-to-json-schema 停止维护说明(2025-11):见其 npm README
- 许可:zod MIT、SDK MIT