5月28日 06:42
MCP 工具定义和参数验证怎么做?
MCP(Model Context Protocol)的工具定义和参数验证,是构建可靠 MCP 服务的基础。如果工具定义不清晰或参数验证不严格,LLM 调用时可能传入错误参数,导致服务崩溃或返回异常结果。
本文从 MCP 协议规范出发,详解工具定义结构、JSON Schema 参数约束、Pydantic 验证实践,以及安全防护要点。
工具定义的核心结构
MCP 工具由三个必填字段组成:name(工具名称)、description(功能描述)和 inputSchema(输入参数的 JSON Schema)。
json{ "name": "query_database", "description": "在指定数据库中执行 SQL 查询,仅支持 SELECT 语句。结果以表格形式返回,最多 1000 行,超时 30 秒。", "inputSchema": { "type": "object", "properties": { "sql": { "type": "string", "description": "SQL 查询语句,仅支持 SELECT", "minLength": 1, "maxLength": 5000 }, "database": { "type": "string", "enum": ["production", "staging", "analytics"], "description": "目标数据库名称" } }, "required": ["sql"], "additionalProperties": false } }
关键要点:
additionalProperties: false是安全必备项。不加这个约束,LLM 可能传入预期之外的参数字段,引发未知行为description要具体且包含边界信息:说明工具能做什么、不能做什么、返回格式、超时限制等,帮助 LLM 准确选择工具enum约束枚举值:对于有限选项的参数,用enum替代自由字符串,减少 LLM 幻觉导致的错误调用
JSON Schema 支持的参数类型
MCP 的 inputSchema 遵循 JSON Schema 规范,支持以下类型:
| 类型 | 说明 | 常用约束 |
|---|---|---|
string | 字符串 | minLength, maxLength, pattern, format, enum |
number | 数值(整数或浮点) | minimum, maximum, exclusiveMinimum, exclusiveMaximum |
integer | 整数 | minimum, maximum, multipleOf |
boolean | 布尔值 | default |
array | 数组 | minItems, maxItems, items, uniqueItems |
object | 对象 | properties, required, additionalProperties |
嵌套对象示例
实际场景中,参数经常需要嵌套结构:
json{ "name": "search_documents", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词", "minLength": 1, "maxLength": 500 }, "filters": { "type": "object", "properties": { "category": {"type": "string"}, "date_range": { "type": "object", "properties": { "start": {"type": "string", "format": "date"}, "end": {"type": "string", "format": "date"} }, "required": ["start", "end"], "additionalProperties": false } }, "additionalProperties": false }, "sort": { "type": "string", "enum": ["relevance", "date", "popularity"], "default": "relevance" }, "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 } }, "required": ["query"], "additionalProperties": false } }
注意每一层 object 都应设置 additionalProperties: false,包括嵌套的 filters 和 date_range。
用 Pydantic 实现参数验证
手写验证器容易遗漏边界条件,MCP Python SDK 推荐使用 Pydantic 自动生成 schema 并完成验证:
pythonfrom pydantic import BaseModel, Field, field_validator from enum import Enum class DatabaseName(str, Enum): PRODUCTION = "production" STAGING = "staging" ANALYTICS = "analytics" class QueryParams(BaseModel): sql: str = Field( ..., min_length=1, max_length=5000, description="SQL 查询语句,仅支持 SELECT" ) database: DatabaseName = Field( default=DatabaseName.PRODUCTION, description="目标数据库名称" ) @field_validator("sql") @classmethod def validate_sql_read_only(cls, v: str) -> str: upper = v.strip().upper() forbidden = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE"] for keyword in forbidden: if keyword in upper: raise ValueError(f"不允许执行 {keyword} 操作,仅支持 SELECT 查询") return v # Pydantic 模型自动生成 JSON Schema schema = QueryParams.model_json_schema()
Pydantic 的优势:
- 类型自动推断:Python 类型注解直接映射为 JSON Schema 类型
- 验证规则内聚:约束和自定义验证逻辑与数据模型放在一起,维护方便
- 错误信息清晰:验证失败时返回结构化错误,包含字段路径和具体原因
- 与 MCP SDK 集成:
@mcp.tool()装饰器可直接使用 Pydantic 模型作为参数类型
与 MCP SDK 集成示例
pythonfrom mcp.server.fastmcp import FastMCP mcp = FastMCP("database-service") @mcp.tool() async def query_database(params: QueryParams) -> str: """在指定数据库中执行 SQL 查询""" # params 已经过 Pydantic 验证,可以直接使用 result = await execute_sql(params.sql, params.database.value) return format_result(result)
format 格式验证
JSON Schema 的 format 字段可以对字符串做语义验证:
pythonfrom pydantic import BaseModel, Field, EmailStr, HttpUrl from datetime import date class UserProfile(BaseModel): email: EmailStr = Field(description="用户邮箱") website: HttpUrl = Field(default=None, description="个人网站") birthday: date = Field(description="出生日期")
常用 format 值:
| format | 说明 | 示例 |
|---|---|---|
date | 日期 | 2026-05-28 |
date-time | 日期时间 | 2026-05-28T10:30:00Z |
email | 邮箱地址 | user@example.com |
uri | URI | https://example.com |
uuid | UUID | 550e8400-e29b-41d4-a716-446655440000 |
工具调用的错误处理
参数验证失败时,应返回结构化错误信息,而非抛出异常让服务崩溃:
pythonfrom mcp.server.fastmcp import FastMCP from pydantic import ValidationError mcp = FastMCP("database-service") @mcp.tool() async def query_database(sql: str, database: str = "production") -> str: """在指定数据库中执行 SQL 查询,仅支持 SELECT""" try: params = QueryParams(sql=sql, database=database) except ValidationError as e: return f"参数验证失败: {e.json()}" try: result = await execute_sql(params.sql, params.database.value) return format_result(result) except TimeoutError: return "查询超时(30 秒限制),请简化查询或添加更严格的过滤条件" except Exception as e: return f"查询执行失败: {type(e).__name__}: {str(e)}"
安全防护要点
工具定义和参数验证不只是功能需求,更是安全防线。LLM 生成的参数不可信任,以下实践必须遵守:
- 每个 object 都加
additionalProperties: false:防止 LLM 注入未定义字段 - 字符串参数设
maxLength:防止超长输入导致缓冲区溢出或 DoS - 数值参数设
minimum/maximum:防止极端值导致计算异常 - 敏感操作加确认机制:涉及写入、删除等操作,在服务端实现二次确认
- 对工具定义做哈希校验:防止恶意 MCP 服务在审核后篡改工具 schema(tool poisoning 攻击)
- 日志记录所有调用:记录工具名、参数(脱敏)、结果、时间戳,便于审计追踪
总结
| 要点 | 做法 |
|---|---|
| 工具定义 | 必填 name + description + inputSchema,description 要具体 |
| 参数约束 | 用 JSON Schema 的 type、enum、minLength 等做声明式约束 |
| 代码验证 | 优先用 Pydantic 模型,自动生成 schema + 运行时验证 |
| 安全防护 | additionalProperties: false + maxLength + 哈希校验 + 审计日志 |
| 错误处理 | 捕获 ValidationError,返回结构化错误,不要让服务崩溃 |