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,包括嵌套的 filtersdate_range

用 Pydantic 实现参数验证

手写验证器容易遗漏边界条件,MCP Python SDK 推荐使用 Pydantic 自动生成 schema 并完成验证:

python
from 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 集成示例

python
from 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 字段可以对字符串做语义验证:

python
from 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
uriURIhttps://example.com
uuidUUID550e8400-e29b-41d4-a716-446655440000

工具调用的错误处理

参数验证失败时,应返回结构化错误信息,而非抛出异常让服务崩溃:

python
from 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 生成的参数不可信任,以下实践必须遵守:

  1. 每个 object 都加 additionalProperties: false:防止 LLM 注入未定义字段
  2. 字符串参数设 maxLength:防止超长输入导致缓冲区溢出或 DoS
  3. 数值参数设 minimum/maximum:防止极端值导致计算异常
  4. 敏感操作加确认机制:涉及写入、删除等操作,在服务端实现二次确认
  5. 对工具定义做哈希校验:防止恶意 MCP 服务在审核后篡改工具 schema(tool poisoning 攻击)
  6. 日志记录所有调用:记录工具名、参数(脱敏)、结果、时间戳,便于审计追踪

总结

要点做法
工具定义必填 name + description + inputSchema,description 要具体
参数约束用 JSON Schema 的 typeenumminLength 等做声明式约束
代码验证优先用 Pydantic 模型,自动生成 schema + 运行时验证
安全防护additionalProperties: false + maxLength + 哈希校验 + 审计日志
错误处理捕获 ValidationError,返回结构化错误,不要让服务崩溃
标签:MCP