Model Context Protocol 服务器中的 JSON Schema 使用模式
Model Context Protocol (MCP) 服务器广泛使用 JSON Schema 来定义工具参数、验证输入并确保客户端-服务器通信中的类型安全。本综合指南通过分析生产服务器和官方规范中的实际实现模式,为开发者提供构建健壮 MCP 实现的可操作见解。
MCP 的 TypeScript 优先架构
Model Context Protocol 采用TypeScript 优先的方法,其中权威的 Schema 定义源于 TypeScript 代码,然后自动生成 JSON Schema 以确保更广泛的兼容性。这种设计理念确保了 TypeScript 实现中的类型安全,同时为其他语言提供通用的 JSON Schema 验证。
在官方 MCP 规范中,JSON Schema 出现在四个关键领域。工具使用 inputSchema
定义接受的参数,使用 outputSchema
(在规范 2025-06-18 中引入)验证结构化响应。资源使用 Schema 进行模板定义和内容验证。提示验证参数结构和消息格式。整个协议消息结构本身使用 JSON Schema 定义,涵盖请求/响应格式、通知、错误处理和能力协商。
当前规范有一个值得注意的限制:structuredContent
必须是对象类型,尽管 JSON Schema 具有更广泛的能力,但阻止工具返回数组或原始类型。社区提案 #834 旨在通过建立完整的 JSON Schema 2020-12 支持来解决这个问题,这将消除仅限对象的限制并启用高级验证组合。
Zod 与原生 JSON Schema 方法对比
现代 MCP 实现绝大多数倾向于使用 Zod 而不是原生 JSON Schema 进行 Schema 定义,这主要是由于更优秀的开发者体验和自动类型推断。以下是实际对比:
// Zod 方法 - TypeScript 环境中的首选
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const weatherSchema = z.object({
location: z.string().describe("城市名称或邮政编码"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
includeForcast: z.boolean().optional()
});
server.registerTool("get_weather", {
description: "获取当前天气数据",
inputSchema: weatherSchema
}, async (args) => {
// TypeScript 自动推断参数类型
// args.location 是字符串,args.units 是 "celsius" | "fahrenheit"
const validated = weatherSchema.parse(args);
// 具有完整类型安全的实现
});
原生 JSON Schema 方法虽然更冗长,但提供了跨语言兼容性:
// 原生 JSON Schema 方法
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [{
name: "get_weather",
description: "获取当前天气数据",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description: "城市名称或邮政编码"
},
units: {
type: "string",
enum: ["celsius", "fahrenheit"],
default: "celsius"
}
},
required: ["location"],
additionalProperties: false
}
}]
};
});
Zod 方法提供自动 TypeScript 类型推断、内置运行时验证和详细错误消息、出色的 IntelliSense 支持以及零外部依赖。但是,它需要 TypeScript 环境并增加了学习曲线。原生 JSON Schema 提供协议原生兼容性,适用于所有编程语言,并与现有 JSON Schema 工具集成,但缺乏类型安全性并需要手动验证实现。
MCP 工具中的常见 Schema 模式
生产 MCP 服务器一致采用几种平衡灵活性与验证严格性的 Schema 模式。最普遍的模式处理带默认值的可选参数:
const toolSchema = z.object({
// 带验证的必需参数
query: z.string().min(1).describe("搜索查询"),
// 带默认值和约束的可选参数
limit: z.number().min(1).max(100).default(10)
.describe("返回的最大结果数"),
// 无默认值的可选参数
category: z.string().optional()
.describe("按类别筛选"),
// 混合要求的嵌套对象
options: z.object({
includeMetadata: z.boolean().default(false),
sortBy: z.enum(["relevance", "date", "popularity"]).default("relevance")
}).optional()
});
对于复杂嵌套结构,MCP 工具通常建模分层数据:
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string().regex(/^\d{5}(-\d{4})?$/),
country: z.string().default("US")
});
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
addresses: z.array(addressSchema).min(1),
preferences: z.record(z.string(), z.any()).optional()
});
工具参数定义的最佳实践
有效的参数定义需要仔细注意验证、文档和错误处理。输入清理防止安全漏洞:
const secureInputSchema = z.object({
filename: z.string()
.regex(/^[a-zA-Z0-9._-]+$/, "无效的文件名字符")
.refine(name => !name.startsWith('.'), "不允许隐藏文件"),
sqlQuery: z.string()
.refine(query => {
const dangerous = ['drop', 'delete', 'truncate'];
return !dangerous.some(word =>
query.toLowerCase().includes(word)
);
}, "查询包含危险操作"),
userInput: z.string()
.trim()
.min(1, "输入不能为空")
.max(1000, "输入超过最大长度")
.transform(input => input.replace(/[<>]/g, ""))
});
丰富的描述增强 LLM 理解:
const documentedSchema = z.object({
query: z.string()
.describe("搜索查询 - 支持通配符 (*) 和引号中的精确短语"),
dateRange: z.object({
start: z.string().datetime()
.describe("ISO 8601 格式的开始日期 (YYYY-MM-DDTHH:mm:ssZ)"),
end: z.string().datetime()
.describe("ISO 8601 格式的结束日期 (YYYY-MM-DDTHH:mm:ssZ)")
}).optional()
.describe("按日期范围筛选结果 - 必须提供两个日期"),
sortBy: z.enum(["relevance", "date", "popularity"])
.default("relevance")
.describe("排序方式:relevance(最佳匹配),date(最新优先),popularity(最多查看)")
});
MCP 中的高级 JSON Schema 功能
虽然基本 Schema 足以满足大多数工具,但复杂的 MCP 服务器利用高级 JSON Schema 功能。但是,客户端兼容性存在显著差异。
使用 allOf 进行 Schema 组合
allOf
关键字在地址工具中启用特定国家的验证:
{
"type": "object",
"properties": {
"street_address": {"type": "string"},
"country": {"enum": ["US", "Canada", "Netherlands"]}
},
"allOf": [
{
"if": {"properties": {"country": {"const": "US"}}},
"then": {"properties": {"postal_code": {"pattern": "[0-9]{5}(-[0-9]{4})?"}}
},
{
"if": {"properties": {"country": {"const": "Canada"}}},
"then": {"properties": {"postal_code": {"pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]"}}}
}
]
}
使用 anyOf 进行联合类型
数据库连接工具使用 anyOf
进行灵活配置:
const connectionSchema = z.union([
z.object({
type: z.literal("postgresql"),
host: z.string(),
port: z.number(),
database: z.string(),
username: z.string(),
password: z.string()
}),
z.object({
type: z.literal("sqlite"),
file_path: z.string()
}),
z.object({
type: z.literal("connection_string"),
connection_string: z.string()
})
]);
客户端限制的解决方案
不同的 MCP 客户端具有不同级别的 JSON Schema 支持。Google 代理仅支持 anyOf
(不支持 allOf
或 oneOf
),Gemini API 完全不支持联合类型,而 Cursor 将服务器限制为 40 个工具,且名称总长度为 60 个字符。生产服务器实现能力检测:
function transformSchemaForClient(schema, clientType) {
const capabilities = {
"claude-desktop": ["refs", "unions", "formats"],
"cursor": ["basic"],
"google-agents": ["anyof-only"],
"gemini": ["basic", "no-unions"]
};
if (!capabilities[clientType].includes("unions")) {
// 移除 anyOf/oneOf,仅使用第一个变体
schema = removeUnions(schema);
}
if (!capabilities[clientType].includes("refs")) {
// 内联所有 $ref 引用
schema = inlineReferences(schema);
}
return schema;
}
验证策略和实现
MCP 服务器主要使用 Zod 进行运行时验证,而不是传统的 JSON Schema 验证器如 AJV。这种模式来自官方 SDK 和生产实现:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const UserFilterSchema = z.object({
userId: z.string().describe("要筛选的用户 ID"),
page: z.number().int().positive().optional().default(1),
limit: z.number().int().positive().optional().default(10)
});
server.registerTool("getUserData", {
inputSchema: UserFilterSchema
}, async (args) => {
try {
const validated = UserFilterSchema.parse(args);
// 使用类型安全进行处理
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (error) {
if (error instanceof z.ZodError) {
const details = error.errors.map(e =>
`${e.path.join('.')}: ${e.message}`
).join(', ');
return {
content: [{ type: "text", text: `验证失败:${details}` }],
isError: true
};
}
throw error;
}
});
为了性能优化,缓存编译的验证器:
const validatorCache = new Map<string, z.ZodSchema>();
function getValidator(toolName: string, schema: z.ZodSchema) {
if (!validatorCache.has(toolName)) {
validatorCache.set(toolName, schema);
}
return validatorCache.get(toolName)!;
}
从 JSON Schema 生成类型
在处理现有 JSON Schema 规范时,MCP 服务器使用 json-schema-to-typescript 生成 TypeScript 接口:
import { compile } from 'json-schema-to-typescript';
const schema = {
title: "MCPToolInput",
type: "object",
properties: {
query: { type: "string" },
filters: {
type: "array",
items: { type: "string" }
},
options: {
type: "object",
properties: {
timeout: { type: "number" },
retry: { type: "boolean" }
}
}
},
required: ["query"],
additionalProperties: false
};
const typescript = await compile(schema, 'MCPToolInput', {
bannerComment: '/* 自动生成的 MCP 工具类型 */',
unreachableDefinitions: true // 对于具有 $refs 的 Schema 很重要
});
// 生成:
// export interface MCPToolInput {
// query: string;
// filters?: string[];
// options?: {
// timeout?: number;
// retry?: boolean;
// };
// }
对于双向类型安全,首先定义类型然后派生 Schema:
interface CreateUserRequest {
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
}
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'user', 'guest'])
}) satisfies z.ZodType<CreateUserRequest>;
// 在编译时验证类型兼容性
type SchemaType = z.infer<typeof createUserSchema>;
const _typeCheck: SchemaType extends CreateUserRequest ? true : false = true;
JSON Schema 版本兼容性
MCP 规范目前缺乏明确的 JSON Schema 版本声明,导致客户端间验证不一致。不同的实现假设不同的版本 - 一些使用 Draft-07,其他尝试 2020-12 支持。这种版本模糊性导致验证失败和运行时错误。
社区提案 #834 旨在建立 JSON Schema 2020-12 作为默认方言,并支持明确的 $schema
字段。这将启用高级验证组合(anyOf、oneOf、allOf)、unevaluatedProperties 进行更严格的验证,以及移除结构化内容的仅限对象限制。
在官方 2020-12 支持到来之前,开发者应该使用在所有客户端中都能工作的保守 Schema 功能,针对多个 MCP 客户端实现测试 Schema,并为未来兼容性准备迁移路径。
错误处理和验证模式
MCP 定义了一个结构化的错误响应格式,所有工具都应该遵循:
interface MCPErrorResponse {
isError: true;
content: Array<{
type: "text";
text: string;
}>;
}
function handleValidationError(error: ZodError): MCPErrorResponse {
const errorDetails = error.errors.map(err => ({
field: err.path.join('.'),
message: err.message,
code: err.code
}));
const userFriendlyMessage = `在 ${errorDetails.length} 个字段上验证失败:\n${
errorDetails.map(detail => `- ${detail.field}: ${detail.message}`).join('\n')
}`;
return {
isError: true,
content: [{
type: "text",
text: userFriendlyMessage
}]
};
}
为了生产弹性,为外部依赖实现断路器:
class CircuitBreaker {
private failures = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
throw new Error('服务暂时不可用');
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = 'closed';
}
private onFailure() {
this.failures++;
if (this.failures >= 3) {
this.state = 'open';
setTimeout(() => this.state = 'half-open', 60000);
}
}
}
真实世界的实现示例
生产 MCP 服务器展示了复杂的 Schema 模式。GitHub MCP 服务器使用带联合类型的复杂嵌套 Schema:
{
"name": "create_pull_request",
"inputSchema": {
"type": "object",
"properties": {
"repo": {"type": "string"},
"title": {"type": "string"},
"body": {"type": "string"},
"reviewers": {
"anyOf": [
{"type": "array", "items": {"type": "string"}},
{"type": "null"}
]
}
},
"required": ["repo", "title"]
}
}
Ultimate MCP 服务器实现带动态 Schema 的多提供商路由:
const multiProviderSchema = z.object({
prompt: z.string(),
providers: z.array(z.object({
provider: z.enum(["google", "anthropic", "gemini", "deepseek"]),
model: z.string(),
temperature: z.number().min(0).max(2).optional()
})).min(1),
commonParameters: z.record(z.string(), z.any()).optional()
});
开发者提示和建议
对于 Schema 设计:在 TypeScript 项目中从 Zod 开始,以利用自动类型推断。使用描述性字段名称和全面的描述来帮助 LLM 理解参数目的。实现带有有意义错误消息的适当验证。在适当的地方提供合理的默认值。
对于验证:优先选择 Zod 而不是 AJV 以获得更好的 TypeScript 集成。实现带有全面错误报告的请求时验证。缓存编译的验证器以提高性能。使用动态 Schema 注入进行上下文感知验证。
对于类型安全:首先定义 Schema,然后派生 TypeScript 类型。使用 satisfies
操作符确保双向类型兼容性。将类型生成集成到构建管道中。在编译时安全之外实现运行时类型检查。
对于错误处理:始终返回带有 isError
标志的结构化错误响应。提供清晰、可操作的错误消息。为外部依赖实现断路器。优雅地处理部分失败。在服务器端记录完整的错误详细信息,同时清理客户端消息。
对于生产部署:针对多个 MCP 客户端测试 Schema。实现客户端能力检测。为有限的客户端提供回退 Schema。监控验证失败率。清楚地记录 Schema 要求。
Model Context Protocol 的 JSON Schema 集成为构建类型安全、验证的工具接口提供了强大的基础。虽然当前版本兼容性和客户端支持方面的限制带来了挑战,但生态系统正在快速成熟。通过遵循这些模式和最佳实践,开发者可以创建既强大又有弹性的 MCP 服务器,准备随着协议的未来增强而发展。