完整练习:把生成草稿收紧成一个输入契约
沿用上面的 create_task 样本。默认生成的 inputSchema 只限制已有字段的类型,所以空对象 {}、priority 为 1.5,或多出一个 owner 字段,都能通过这份草稿的校验。下面先明确业务规则,再手工修改下载或复制出的 JSON;本页生成器的输出和计算逻辑没有改变。
本练习规定:title 和 priority 都必须提供;title 为长度 1 至 120 的字符串;priority 为 1 至 3 的整数;不接受其他字段。这些是这个虚构任务工具的示例规则,不是 MCP 对所有工具的统一要求。下列完整对象另存为 create-task.tool.json。
{
"name": "create_task",
"description": "Teaching example: title is 1 to 120 characters; priority is an integer from 1 to 3.",
"inputSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 3
}
},
"required": ["title", "priority"],
"additionalProperties": false
}
}
required 决定字段是否必须出现,type 决定出现后的值类型,长度和数值范围再约束具体值。additionalProperties: false 拒绝本层未声明的字段;以后增加嵌套对象时,需要在对应的对象 schema 中另行设计约束。校验器应读取这个对象的 inputSchema,而不是拿包含 name 的整份工具定义去校验任务参数。
在本地同时验证草稿与手工版本
将下面完整脚本另存为 check_schema_example.py,与 create-task.tool.json 放在同一目录。它只校验合成 JSON,不创建任务、不连接 MCP Server。脚本中的 draft_schema 对应上方已记录的默认生成结果;strict_schema 读取你刚保存的手工版本。
"""Validate synthetic inputs only. Does not connect to or run an MCP server."""
import json
from pathlib import Path
from jsonschema import Draft202012Validator
ROOT = Path(__file__).resolve().parent
tool = json.loads((ROOT / "create-task.tool.json").read_text(encoding="utf-8"))
strict_schema = tool["inputSchema"]
# This is the inputSchema from this page's recorded default generator output.
draft_schema = {
"type": "object",
"properties": {"title": {"type": "string"}, "priority": {"type": "number"}},
}
for schema in (draft_schema, strict_schema):
Draft202012Validator.check_schema(schema)
draft = Draft202012Validator(draft_schema)
strict = Draft202012Validator(strict_schema)
# label, JSON text, expected draft acceptance, expected strict acceptance
cases = [
("valid", '{"title":"review pr","priority":1}', True, True),
("empty-object", '{}', True, False),
("missing-priority", '{"title":"review pr"}', True, False),
("fraction", '{"title":"review pr","priority":1.5}', True, False),
("below-range", '{"title":"review pr","priority":0}', True, False),
("above-range", '{"title":"review pr","priority":4}', True, False),
("extra-key", '{"title":"review pr","priority":1,"owner":"demo"}', True, False),
("empty-title", '{"title":"","priority":1}', True, False),
("null-title", '{"title":null,"priority":1}', False, False),
("string-priority", '{"title":"review pr","priority":"1"}', False, False),
("boolean-priority", '{"title":"review pr","priority":true}', False, False),
("numeric-one-point-zero", '{"title":"review pr","priority":1.0}', True, True),
("space-only-title", '{"title":" ","priority":1}', True, True),
("upper-bound", json.dumps({"title": "x" * 120, "priority": 3}), True, True),
("title-too-long", json.dumps({"title": "x" * 121, "priority": 1}), True, False),
]
for label, raw, expected_draft, expected_strict in cases:
value = json.loads(raw)
actual = (draft.is_valid(value), strict.is_valid(value))
assert actual == (expected_draft, expected_strict), (label, actual)
print(f"{label}: draft={actual[0]} strict={actual[1]}")
print(f"PASS: {len(cases)} synthetic cases")
本次使用 Python 3.12.14 与 jsonschema 4.26.0 实际运行了这 15 个输入。已有该依赖时可直接运行脚本;新建环境时,可在放有两份文件的目录执行以下命令。安装依赖需要联网,随后这组验证不需要网络或账号。
python3.12 -m venv .venv
.venv/bin/python -m pip install "jsonschema==4.26.0"
.venv/bin/python check_schema_example.py
Windows 可用 py -3.12 -m venv .venv 创建环境,再把后两行的 .venv/bin/python 换成 .venv\Scripts\python.exe。
valid: draft=True strict=True
empty-object: draft=True strict=False
missing-priority: draft=True strict=False
fraction: draft=True strict=False
below-range: draft=True strict=False
above-range: draft=True strict=False
extra-key: draft=True strict=False
empty-title: draft=True strict=False
null-title: draft=False strict=False
string-priority: draft=False strict=False
boolean-priority: draft=False strict=False
numeric-one-point-zero: draft=True strict=True
space-only-title: draft=True strict=True
upper-bound: draft=True strict=True
title-too-long: draft=True strict=False
PASS: 15 synthetic cases
哪些输入被拒绝,哪些仍然允许
- 空对象与缺少 priority 的输入被 required 拒绝;1.5 被 integer 拒绝,0 和 4 被范围约束拒绝。
- 多余的 owner 被 additionalProperties 拒绝;空标题与 121 字符标题被长度约束拒绝。null 标题、字符串优先级和布尔优先级在两份 schema 下都被拒绝。
- JSON 数值 1.0 仍满足 integer,因为它没有非零小数部分。只有一个空格的标题也满足本例的长度规则;若业务不接受空白标题,应再设计相应规则与测试,不能把 minLength 当作自动去空格。
15 行结果中的 True 表示通过对应 schema,False 表示被拒绝;末行 PASS 表示结果与这组预期逐项一致。通过结构校验不会注册工具,也不会授予创建任务的权限。接入真实服务前,还要在服务端落实同一契约,并按目标 SDK 和宿主完成验收。
固定依据:JSON Schema Draft 2020-12 Validation(6.1.1 type、6.2 数值范围、6.3 字符串长度、6.5.3 required),以及Draft 2020-12 Core(10.3.2.3 additionalProperties)。