MCPでToolを作るときに重要なのがinputSchemaです。たとえば、
search_products
というToolを作ったとしても、Tool名だけではMCP ClientやAIモデルは、
何を引数として渡せばよいのか どの項目が必須なのか 文字列なのか数値なのか どの値を選べばよいのか
を判断できません。そこでTool DefinitionにinputSchemaを設定します。inputSchemaは、MCP Toolが受け取るargumentsの構造をJSON Schemaで定義するものです。
2026年7月28日のMCP仕様では、ToolのinputSchemaとoutputSchemaがJSON Schema Draft 2020-12へ正式に拡張されました。inputSchemaのRootは引き続きtype: "object"である必要がありますが、その内部ではoneOf、anyOf、allOf、if / then / else、$defs / $refなどJSON Schema 2020-12の機能を利用できます。
この記事では、MCP ToolのinputSchemaの基本構造から、Python SDK・Pydanticでの自動生成、oneOf・anyOf・条件Schemaといった実例まで解説します。inputSchemaが原因で発生するInvalid Paramsエラーの切り分け方法はMCPサーバーで「Invalid Params」が出る原因で詳しく解説しているため、この記事では正しいSchemaの書き方にしぼります。
- inputSchemaはToolのargumentsを定義する
- 最小のinputSchemaはtype object
- properties・required・enumの基本
- descriptionはモデルが引数を選ぶためにも重要
- 数値・文字列の制約はminimum・pattern・minLengthで表現する
- 配列はtype arrayとitemsを使う
- Nested Objectも定義できる
- Optionalとnullは別物
- defaultを書いても自動的に値が入るとは限らない
- Python SDKならinputSchemaを自動生成できる
- PydanticのFieldで制約やdescriptionを付ける
- 複雑な引数はPydantic Modelへまとめる
- 2026-07-28では$defsと$refも正式に利用できる
- oneOfで「どちらか一方」を表現できる
- oneOfとanyOfは意味が違う
- if・then・elseで条件付きSchemaも作れる
- inputSchemaを複雑にしすぎない
- Property名は曖昧にしない
- booleanで曖昧なモード切り替えを増やしすぎない
- inputSchemaだけで認可やBusiness Ruleを保証しない
- tools/listのinputSchemaを直接確認する
- 2025世代Clientとの互換性にも注意する
- $schemaを省略した場合は2020-12として扱われる
- MCPツールのinputSchemaに関するよくある質問
- inputSchemaは「Validation」と「モデルへの説明」を兼ねている
inputSchemaはToolのargumentsを定義する
MCP ClientがTool一覧を取得するときには、tools/listを使用します。ServerはTool名やDescriptionと一緒にinputSchemaを返します。たとえば次のようなToolです。
{
"name": "search_products",
"description": "商品を検索します",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string"
}
},
"required": [
"query"
]
}
}
AIモデルはこのSchemaを見て、
{
"query": "RTX 5070"
}
というArgumentsを組み立てられます。実際のtools/callは概念的に次のようになります。
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_products",
"arguments": {
"query": "RTX 5070"
}
}
}
つまりinputSchemaで定義しているのはparams全体ではありません。この中のargumentsだけです。inputSchemaと実際のargumentsが一致していないとInvalid Paramsエラーの原因になります。
最小のinputSchemaはtype object
引数を必要としないToolでも、inputSchemaのRootはObjectとして定義します。たとえば現在時刻を返すだけのToolなら、
{
"type": "object",
"properties": {}
}
とできます。2026-07-28仕様ではTool Schema全体がJSON Schema 2020-12へ拡張されましたが、inputSchemaについてはRoot Objectという制約が維持されています。そのため{"type": "string"}をToolのinputSchema Rootにするのは適切ではありません。1つの文字列だけ受け取りたい場合でも、
{
"type": "object",
"properties": {
"query": {
"type": "string"
}
},
"required": [
"query"
]
}
のようにObjectで包みます。
properties・required・enumの基本
Toolへ渡せる項目はpropertiesへ定義し、必須項目はrequiredで指定します。JSON Schemaではpropertiesに書くだけではPropertyは必須になりません。
{
"type": "object",
"properties": {
"query": {
"type": "string"
},
"limit": {
"type": "integer"
}
},
"required": [
"query"
]
}
選択肢を限定したい場合はenumを使うと、Tool Callのブレを減らせます。required・enum・additionalPropertiesで実際に起きやすい間違いや、Schemaと関数定義のズレが原因のエラーはMCPサーバーで「Invalid Params」が出る原因にまとまっています。ここでは設計側の考え方を中心に解説します。
descriptionはモデルが引数を選ぶためにも重要
型だけ定義しても、その引数が何を意味するのかモデルには分からないことがあります。たとえば、
{
"type": "object",
"properties": {
"status": {
"type": "string"
}
}
}
だけでは、statusとは何の状態なのか、どんな値を入れるべきなのかが不明です。そこでdescriptionを書きます。
{
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "検索する注文状態。pending、paid、shippedのいずれかを指定します。"
}
}
}
MCP Python SDKでは、Toolの型Hintから生成されたJSON Schemaがtools/listでClientへ送信され、モデルがTool Callを生成するときの契約として利用されます。SchemaはValidationだけでなく、モデルへToolの使い方を伝える役割も持っています。
数値・文字列の制約はminimum・pattern・minLengthで表現する
数値にはminimum・maximum、文字列にはminLength・patternを使えます。たとえば検索件数を1から100までに制限したい場合は、
{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "取得件数。1から100まで指定できます。"
}
}
}
とできます。これはTool Handler内でif limit > 100と確認するより前の、入力契約として利用できます。ただしSchema ValidationだけをBusiness Ruleのすべてに使う必要はありません。たとえば「無料プランでは最大10件、管理者なら最大100件」のようにユーザー権限によって変わる制約は、Tool Handler側でも確認する必要があります。
文字列も同様に条件を付けられます。
{
"type": "string",
"pattern": "^[0-9]{13}$",
"description": "13桁のISBN"
}
のようにpatternで形式を制限できます。空文字を許可したくない場合はminLength: 1を追加します。単にrequiredへ入れただけでは、{"query": ""}という空文字自体は存在しているためRequired条件は満たします。「存在必須」と「空文字禁止」は別の条件です。
配列はtype arrayとitemsを使う
複数の値を受け取りたい場合はArrayを利用できます。たとえば複数Categoryを指定して商品を検索するなら、
{
"type": "object",
"properties": {
"categories": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
となります。入力例は、
{
"categories": [
"gpu",
"cpu",
"memory"
]
}
です。要素数も制限できます。
{
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"maxItems": 5,
"uniqueItems": true
}
これなら少なくとも1件、最大5件までにでき、重複も禁止できます。JSON Schema 2020-12ではArray関連のKeywordも利用できます。
Nested Objectも定義できる
Tool Argumentの中にObjectを入れることもできます。たとえば検索条件をまとめるなら、
{
"type": "object",
"properties": {
"filters": {
"type": "object",
"properties": {
"minPrice": {
"type": "integer",
"minimum": 0
},
"maxPrice": {
"type": "integer",
"minimum": 0
},
"inStock": {
"type": "boolean"
}
}
}
}
}
入力は、
{
"filters": {
"minPrice": 50000,
"maxPrice": 150000,
"inStock": true
}
}
となります。Nested Object側にも独立してrequiredやadditionalPropertiesを設定できます。
{
"type": "object",
"properties": {
"filters": {
"type": "object",
"properties": {
"minPrice": {
"type": "integer"
},
"maxPrice": {
"type": "integer"
}
},
"required": [
"minPrice",
"maxPrice"
],
"additionalProperties": false
}
}
}
JSON SchemaではDefaultでpropertiesに書いていないPropertyも許可されるため、未知の項目を禁止したい場合はadditionalProperties: falseを追加します。将来Fieldを追加する互換性を優先してあえて許可する設計もあります。
Optionalとnullは別物
MCP Tool Schemaでよく混同されるのが、「引数を省略できる」ことと「nullを渡せる」ことです。categoryをrequiredへ入れなければ{}はValidですが、{"category": null}はcategoryがStringではないためInvalidです。nullも許可したいなら、JSON Schema 2020-12では、
{
"type": [
"string",
"null"
]
}
とできます。つまりOptionalは「Property自体がなくてもよい」、Nullableは「Propertyは存在するが値がnullでもよい」という別の意味です。Tool Handlerで省略とnullを別の意味として扱う場合は特に注意してください。
defaultを書いても自動的に値が入るとは限らない
JSON Schemaにはdefault Keywordがあります。
{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"default": 10
}
}
}
とできます。ただしJSON Schemaにおけるdefaultは基本的にAnnotationです。Schemaに"default": 10と書いたからといって、すべてのJSON Schema Validatorが欠けたPropertyへ自動的に10を挿入するわけではありません。JSON Schema 2020-12の仕様でもdefaultはAnnotation Keywordとして定義されています。
SDK側でFunction Defaultを持っている場合は別です。たとえばMCP Python SDKなら、
@mcp.tool()
def search_books(
query: str,
limit: int = 10
) -> str:
...
とすると、SDKが生成するSchemaにもlimitのDefaultが含まれます。現在のPython SDKでは、Default値のないParameterはrequiredになり、Default値を持つParameterはOptionalとしてSchemaへ反映されます。手書きSchemaのdefaultと、実際のHandlerが持つDefault Logicを混同しないよう注意してください。
Python SDKならinputSchemaを自動生成できる
MCP Python SDKのHigh-level APIでは、毎回JSON Schemaを手書きする必要はありません。たとえば、
from mcp.server import MCPServer
mcp = MCPServer("Shop")
@mcp.tool()
def search_products(
query: str,
limit: int = 10
) -> str:
"""商品をキーワードで検索します。"""
return f"{query}: {limit}"
とします。SDKは型Hintから概念的に次のようなinputSchemaを生成します。
{
"type": "object",
"properties": {
"query": {
"type": "string",
"title": "Query"
},
"limit": {
"type": "integer",
"default": 10,
"title": "Limit"
}
},
"required": [
"query"
]
}
MCP Python SDKの公式ドキュメントでも、型HintからJSON Schemaを生成してtools/listでClientへ送信する仕組みが説明されています。単純なToolなら、自分でSchemaを二重管理するよりSDKへ生成させたほうがHandlerとのズレを防ぎやすくなります。
PydanticのFieldで制約やdescriptionを付ける
PythonではPydanticを利用してより詳細なSchemaを生成できます。たとえば、
from typing import Annotated
from mcp.server import MCPServer
from pydantic import Field
mcp = MCPServer("Shop")
@mcp.tool()
def search_products(
query: Annotated[
str,
Field(
min_length=1,
description="検索する商品名またはキーワード"
)
],
limit: Annotated[
int,
Field(
ge=1,
le=100,
description="最大取得件数"
)
] = 10
) -> str:
return f"{query}: {limit}"
といった構成にできます。これによってminLength・minimum・maximum・description・defaultなどをSchemaへ反映できます。Argumentが複雑になった場合はPydantic Modelへまとめる方法もあります。
複雑な引数はPydantic Modelへまとめる
たとえば商品登録Toolに、商品名・価格・カテゴリ・タグ・在庫を渡すとします。この場合はModelにまとめると管理しやすくなります。
from pydantic import BaseModel, Field
from mcp.server import MCPServer
mcp = MCPServer("Shop")
class ProductInput(BaseModel):
name: str = Field(
min_length=1,
description="商品名"
)
price: int = Field(
ge=0,
description="税込価格"
)
category: str = Field(
description="商品カテゴリ"
)
tags: list[str] = []
in_stock: bool = True
@mcp.tool()
def create_product(
product: ProductInput
) -> str:
return f"Created {product.name}"
現行MCP Python SDKではPydantic ModelをTool Parameterとして使用でき、Model Schemaは$defsと$refを利用してToolのinputSchemaへ組み込まれます。Handler側でもValidation済みのProductInputを受け取れるため、複雑な入力ほど便利です。
2026-07-28では$defsと$refも正式に利用できる
2026年7月28日の仕様では、Tool SchemaがFull JSON Schema 2020-12になりました。そのため共通Objectを$defsへ定義し、$refから参照できます。たとえばAddressを共通化するなら、
{
"type": "object",
"$defs": {
"address": {
"type": "object",
"properties": {
"postalCode": {
"type": "string"
},
"city": {
"type": "string"
},
"street": {
"type": "string"
}
},
"required": [
"city",
"street"
]
}
},
"properties": {
"billingAddress": {
"$ref": "#/$defs/address"
},
"shippingAddress": {
"$ref": "#/$defs/address"
}
}
}
同じNested Schemaを何度もコピーする必要がなくなります。特に自動生成されるPydantic Schemaでも、この形を目にすることがあります。
oneOfで「どちらか一方」を表現できる
たとえば本を、ISBNまたはタイトル+著者のどちらかで検索したいとします。2026-07-28ではoneOfもinputSchemaで正式に利用できます。
{
"type": "object",
"properties": {
"isbn": {
"type": "string",
"pattern": "^[0-9]{13}$"
},
"title": {
"type": "string"
},
"author": {
"type": "string"
}
},
"oneOf": [
{
"required": [
"isbn"
]
},
{
"required": [
"title",
"author"
]
}
]
}
単純なrequiredだけでは表しにくい「どちらか一方の組を必須にする」条件をSchemaへ持たせられます。
oneOfとanyOfは意味が違う
oneOfは、定義したSubschemaのうちちょうど1つに一致する必要があります。一方anyOfは、1つ以上に一致すればValidです。たとえば検索条件として、email・電話番号・顧客IDのどれかがあればよい場合はanyOfを利用できます。
{
"type": "object",
"properties": {
"email": {
"type": "string"
},
"phone": {
"type": "string"
},
"customerId": {
"type": "string"
}
},
"anyOf": [
{
"required": [
"email"
]
},
{
"required": [
"phone"
]
},
{
"required": [
"customerId"
]
}
]
}
複数条件を同時に指定しても問題ないのであればanyOfのほうが自然です。入力パターンを排他的にしたい場合はoneOfを検討します。
if・then・elseで条件付きSchemaも作れる
JSON Schema 2020-12では条件分岐も利用できます。たとえば「deliveryTypeがscheduledならdeliveryDate必須」というRuleです。
{
"type": "object",
"properties": {
"deliveryType": {
"type": "string",
"enum": [
"immediate",
"scheduled"
]
},
"deliveryDate": {
"type": "string",
"format": "date-time"
}
},
"required": [
"deliveryType"
],
"if": {
"properties": {
"deliveryType": {
"const": "scheduled"
}
}
},
"then": {
"required": [
"deliveryDate"
]
}
}
2026-07-28 MCPでは、このようなConditional SchemaもinputSchemaで利用可能になっています。ただしSchemaを複雑にしすぎると、AIモデルにとって引数生成が難しくなる場合があります。Schemaで表現できるからといって、何でも複雑なConditionalにする必要はありません。
inputSchemaを複雑にしすぎない
Schemaは厳密であるほどよいとは限りません。たとえば1つのToolへ検索・作成・更新・削除を全部詰め込み、{"action": "..."}によって巨大なoneOfを切り替える設計もできます。しかしモデルから見るとToolの役割が複雑になります。
それなら、
search_customer create_customer update_customer delete_customer
とToolを分けたほうが、Tool選択もArguments生成も簡単になる場合があります。inputSchemaはAPIのValidationだけでなく、AIが正しいTool Callを作るための説明書でもあります。Schemaの美しさだけでなく、モデルが理解しやすい構造を優先してください。
Property名は曖昧にしない
たとえば、
{
"properties": {
"q": {
"type": "string"
},
"n": {
"type": "integer"
}
}
}
より、
{
"properties": {
"query": {
"type": "string",
"description": "検索するキーワード"
},
"limit": {
"type": "integer",
"description": "最大取得件数"
}
}
}
のほうがモデルにとって意味を判断しやすくなります。通常のAPIでは短いParameter名が問題にならないこともあります。しかしMCPではモデル自身がSchemaを見てArgumentsを組み立てます。descriptionを付けなくてもProperty名だけで意味が伝わる程度に、具体的な名前を付けるのがおすすめです。
booleanで曖昧なモード切り替えを増やしすぎない
たとえば、
{
"properties": {
"force": {
"type": "boolean"
},
"safe": {
"type": "boolean"
},
"dryRun": {
"type": "boolean"
}
}
}
とすると、それぞれの組み合わせによって挙動が分かりにくくなる場合があります。排他的なModeなら、
{
"properties": {
"mode": {
"type": "string",
"enum": [
"dry-run",
"normal",
"force"
]
}
}
}
のほうが意味を明確にできます。特に破壊的Toolでは、force=true・safe=falseのような複雑なBoolean組み合わせをモデルに判断させるより、明示的なEnumのほうが安全です。
inputSchemaだけで認可やBusiness Ruleを保証しない
inputSchemaは入力Validationには有効です。しかし「このユーザーは顧客100を削除できるか」「この注文は現在キャンセル可能か」「本番環境では実行禁止か」といった条件はSchemaだけでは表現できません。
たとえば{"customerId": 100}がSchema上Validでも、そのCustomerへアクセスするPermissionがあるとは限りません。Tool Handler側でも、Authentication・Authorization・Resource Ownership・Current State・Business Ruleを確認してください。Schema ValidationとApplication Validationは別です。
tools/listのinputSchemaを直接確認する
作ったSchemaが実際にどう公開されているか確認したい場合は、MCP Clientからtools/listを実行できます。Python SDKなら、
import anyio
from mcp import Client
async def main() -> None:
async with Client(
"http://localhost:8000/mcp"
) as client:
result = await client.list_tools()
for tool in result.tools:
print(tool.name)
print(tool.input_schema)
if __name__ == "__main__":
anyio.run(main)
とできます。現行Python SDKではlist_tools()で取得した各Toolのinput_schemaから、Serverが実際に公開しているJSON Schemaを確認できます。Pydanticから自動生成している場合は、想像しているSchemaと実際のWire上のSchemaが同じか確認するとよいでしょう。GUIから同じことを確認したい場合はMCP Inspectorの使い方も参照してください。
2025世代Clientとの互換性にも注意する
2026-07-28ではTool SchemaがFull JSON Schema 2020-12になりました。しかし古いMCP Clientが必ず同じSchema機能を完全に理解できるとは限りません。特にoneOf・anyOf・if / then / else・$defs / $refを多用するToolを古いClientへ提供する場合は、実際の対応状況を確認したほうが安全です。
幅広いMCP Clientへの互換性を重視するなら、可能な限り単純なObject Schemaにしておくメリットもあります。新しいSDKへ更新した直後にTool CallやValidationの挙動が変わった場合は、Client側とServer側どちらのSDKバージョンが対応するProtocol Revisionを前提にしているか確認してください。
$schemaを省略した場合は2020-12として扱われる
現在のMCP仕様では、ToolのinputSchemaに$schemaがない場合、JSON Schema 2020-12として扱われます。Python SDKのLow-level Serverドキュメントでも、input_schema・output_schemaはJSON Schemaであり、$schemaを省略したSchemaは2020-12 Dialectとして扱われると明記されています。
そのため、
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object"
}
と毎回明示する必要はありません。MCP SDKによる自動生成Schemaでも通常$schemaは省略されます。
MCPツールのinputSchemaに関するよくある質問
QinputSchemaのRootをtype: “array”やtype: “string”にしてもよいですか
A推奨されません。引数を1つだけ受け取るToolでも、inputSchemaのRootはtype: “object”にする必要があります。配列や文字列を渡したい場合は、object内のpropertyとして包んでください。
QPythonの関数にデフォルト値があると、生成されるSchemaはどうなりますか
Aデフォルト値を持つParameterはSchemaのrequiredに含まれず、Optionalとして扱われます。デフォルト値のないParameterはrequiredへ自動的に追加されます。手書きのdefaultキーワードとは別の仕組みです。
QoneOfとanyOfはどちらを使うべきですか
A入力パターンを排他的にしたい場合(例: ISBNかタイトル+著者のどちらか一方)はoneOfを使います。複数の条件を同時に満たしても問題ない場合(例: emailか電話番号か顧客IDのいずれかがあればよい)はanyOfのほうが自然です。
QJSON Schemaでdefaultを書けば、省略した引数に自動的に値が入りますか
A入りません。JSON Schemaのdefaultは基本的にAnnotationであり、Validatorが自動的に値を挿入することは仕様上保証されていません。MCP Python SDKのようにSDK側で関数のデフォルト値を管理している場合は別ですが、手書きのdefaultキーワードだけに動作を期待しないでください。
QinputSchemaで細かく制約を書けば、権限チェックなどのBusiness Ruleも不要になりますか
Aなりません。inputSchemaはあくまで入力の形式チェックです。特定のユーザーがそのリソースを操作できるか、現在の状態で実行可能かといった判断はSchemaでは表現できないため、Tool Handler側で別途確認する必要があります。
inputSchemaは「Validation」と「モデルへの説明」を兼ねている
MCP ToolのinputSchemaを書くとき、単にServer Side Validationだけを考えると、{"type": "object"}でも実装できてしまう場合があります。しかしこれではモデルが正しいArgumentsを作れません。
良いinputSchemaでは、Property名・type・required・description・enum・数値範囲・配列構造・Nested Objectなどを使って、モデルへ入力形式をできるだけ明確に伝えます。一方で、Tool Schemaを過剰に複雑にする必要もありません。モデルが理解しやすく、Server側が確実にValidationできる最小限のSchemaを目指すのが基本です。
2026-07-28のMCPでは、inputSchemaはFull JSON Schema 2020-12を利用できるようになり、oneOf、anyOf、Conditionals、$defs / $refなども正式に利用できます。ただしRootは引き続きtype: "object"です。
Python SDKなどHigh-level SDKを使っている場合は、単純なToolなら型HintやPydanticからSchemaを自動生成するほうが、Handlerとの不一致を防ぎやすくなります。そして手書きする場合は、まず次のような単純なSchemaから始めるとよいでしょう。
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1,
"description": "検索するキーワード"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 10,
"description": "最大取得件数"
}
},
"required": [
"query"
],
"additionalProperties": false
}
inputSchemaはMCP Toolの単なる型定義ではありません。AIモデルに「このToolをどう呼べばよいか」を教え、Server側でも不正なArgumentsを拒否するための契約として設計することが重要です。実際にInvalid ParamsやSchemaのズレで困ったときはMCPサーバーで「Invalid Params」が出る原因もあわせて参照してください。
