MCP Python SDK v2 Beta: 5 JSON Breaking Changes Before June 30
MCP Python SDK v2.0.0a1 is live, beta drops June 30. Five breaking changes affect how JSON flows through your MCP server: outputSchema validation, JSON Schema 2020-12, stateless sessions, error code remapping, and structuredContent flexibility. Full Python migration guide with code.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →The MCP Python SDK v2.0.0a1 is already on PyPI. The beta drops June 30 — 11 days from today. Stable v2 ships July 27, spec locks July 28.
If your MCP server uses Python, you have 11 days to test five changes that directly affect how JSON flows between your tools, clients, and schemas. This guide covers each one with working Python code and a migration checklist.
For a TypeScript migration or the full list of RC changes, see the MCP 2026 JSON Schema migration guide and the MCP RC July 28 checklist.
Install the Alpha Now
Don't wait for beta. Test your MCP server against v2 today:
pip install "mcp==2.0.0a1"
or pin to any alpha pre-release
pip install "mcp>=2.0.0a0,<2.0.0"
Check what you have:
python -c "import mcp; print(mcp.__version__)"
2.0.0a1
The alpha is API-stable for everything covered in this guide. Beta (June 30) will lock the remaining APIs before stable (July 27).
Change 1: outputSchema Is Now First-Class
This is the most impactful v2 change for tool authors. In MCP 1.x, your tool returned untyped JSON and the client had to figure out the shape. In v2, you declare an outputSchema and FastMCP validates the structured result against it automatically.
# MCP 1.x — client receives an opaque JSON blob
@mcp.tool()
def repair_json(broken: str) -> str:
# Returns a string — client has no schema to validate against
return fix(broken)
v2 (outputSchema from Pydantic model):
from pydantic import BaseModel
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("json-tools")
class RepairResult(BaseModel):
fixed: str
valid: bool
errors_found: int
@mcp.tool()
def repair_json(broken: str) -> RepairResult:
# FastMCP generates outputSchema from RepairResult
# and validates structuredContent before sending
result = fix_and_validate(broken)
return RepairResult(
fixed=result.text,
valid=result.is_valid,
errors_found=result.error_count
)
FastMCP generates this outputSchema automatically:
{
"type": "object",
"properties": {
"fixed": { "type": "string" },
"valid": { "type": "boolean" },
"errors_found": { "type": "integer" }
},
"required": ["fixed", "valid", "errors_found"]
}
What breaks if you skip this: If your tool returns a dict that doesn't match the inferred schema, FastMCP raises a PydanticSchemaGenerationError at registration time, not at runtime. Fix it by returning a typed Pydantic model or a plain dict with output_schema=None to opt out.
If a tool returns malformed JSON that fails outputSchema validation, repair it before it reaches the client — a one-pass fix is faster than debugging validation failures in production.
Change 2: inputSchema Upgrades to JSON Schema 2020-12
MCP 1.x used JSON Schema draft-07 for inputSchema. v2 upgrades to 2020-12. Three specific keyword changes break existing schemas:
| Old (draft-07) | New (2020-12) | Impact |
|---|---|---|
definitions | $defs | Schemas using $ref: #/definitions/... break |
$recursiveRef | $dynamicRef | Self-referencing schemas break |
additionalItems | items (new meaning) | Array schemas with additionalItems: false break |
items: [schema, schema] | prefixItems | Tuple validation syntax changed |
# BEFORE (draft-07 — breaks in MCP v2)
schema = {
"type": "object",
"definitions": {
"Address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"}
}
}
},
"properties": {
"address": {"$ref": "#/definitions/Address"}
}
}
AFTER (2020-12 — works in MCP v2)
schema = {
"type": "object",
"$defs": {
"Address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"}
}
}
},
"properties": {
"address": {"$ref": "#/$defs/Address"}
}
}
Tuple validation fix:
# BEFORE (draft-07 — positional items array)
schema = {
"type": "array",
"items": [{"type": "string"}, {"type": "integer"}],
"additionalItems": False
}
AFTER (2020-12 — prefixItems + no additionalItems)
schema = {
"type": "array",
"prefixItems": [{"type": "string"}, {"type": "integer"}],
"items": False # "items" now means "items after prefixItems"
}
Good news: MCP v2 now allows composition (oneOf, anyOf, allOf, $ref, $defs) in inputSchema — something 1.x explicitly blocked. Tool parameters can now express union types natively.
Change 3: Sessions Removed from Core Protocol
MCP 1.x maintained a session concept at the protocol layer. v2 removes it entirely. The protocol is now stateless by default. Client identity, capabilities, and protocol version travel in the _meta field on every request.
If you were relying on session state in a long-running MCP server (user context, conversation history, per-session tool overrides), you need to move that state out of the protocol layer.
FastMCP makes this explicit withstateless_http=True:
from mcp.server.fastmcp import FastMCP
v2 — stateless HTTP is the recommended mode for deployed servers
mcp = FastMCP("my-server", stateless_http=True)
@mcp.tool()
def repair_json(broken: str, request_id: str | None = None) -> dict:
# Any per-request context must come through tool parameters now
# Sessions no longer carry user state
return {"fixed": fix(broken), "request_id": request_id}
For state you must preserve (auth tokens, user preferences, conversation context), the pattern is now:
- Store in your own database/cache (Redis, PostgreSQL, in-process LRU)
- Pass a lookup key as a tool parameter
- Hydrate from storage on each call
This is a bigger migration for servers that built complex session logic. Stateless is the correct long-term direction — it makes MCP servers horizontally scalable without sticky sessions.
Change 4: Error Code -32002 → -32602
In MCP 1.x, the server returned error code -32002 for invalid parameters (a custom MCP error code). In v2, this is remapped to -32602 — the standard JSON-RPC 2.0 "Invalid Params" error.
# BEFORE — catches MCP 1.x validation errors but MISSES v2
try:
result = await client.call_tool("repair_json", params)
except McpError as e:
if e.code == -32002: # This never fires in MCP v2
handle_invalid_params(e)
AFTER — catches v2 validation errors
try:
result = await client.call_tool("repair_json", params)
except McpError as e:
if e.code == -32602: # JSON-RPC standard "Invalid Params"
handle_invalid_params(e)
elif e.code == -32002: # Keep for backward compat with 1.x servers
handle_invalid_params(e)
Grep your codebase right now:
grep -rn "32002" . --include="*.py"
If you hit zero matches, you're either catching all McpError generically (safe) or not handling this error class at all (audit needed). If you hit matches, update them before v2 stable.
Change 5: structuredContent Now Accepts Any JSON
In MCP 1.x, structuredContent had to be a JSON object (dict). In v2, it can be any JSON value: a string, number, boolean, array, or object.
This unlocks tools that legitimately return arrays or scalars as their primary output:
@mcp.tool()
def extract_json_errors(broken: str) -> list[str]:
# Returns a list — valid structuredContent in MCP v2
# Would have required wrapping in a dict in MCP 1.x
return find_errors(broken)
@mcp.tool()
def count_json_keys(json_str: str) -> int:
# Returns an integer — valid in v2
import json
return len(json.loads(json_str))
If you wrapped arrays in dicts to work around MCP 1.x, you can clean those up:
# BEFORE — unnecessary wrapper
class ErrorList(BaseModel):
errors: list[str]
@mcp.tool()
def extract_errors(text: str) -> ErrorList:
return ErrorList(errors=find_errors(text))
AFTER — return list directly
@mcp.tool()
def extract_errors(text: str) -> list[str]:
return find_errors(text)
Migration Checklist
Run through this before June 30:
- [ ] Install alpha:
pip install "mcp==2.0.0a1"and run your server - [ ] outputSchema: Return typed Pydantic models (or dicts) from all tools. Register tools and confirm no
PydanticSchemaGenerationError - [ ] inputSchema: Grep for
"definitions","$recursiveRef","additionalItems"in hand-written schemas. Replace per the table above - [ ] Pydantic version: Confirm you're on Pydantic v2 (
pip show pydantic→2.x). Pydantic v1 schemas need manual audit - [ ] Error codes:
grep -rn "32002" . --include="*.py"and update to-32602 - [ ] Sessions: Audit any server-side session state. Move to external storage + parameter-based lookup
- [ ] structuredContent type: If you wrapped arrays or primitives in dicts, unwrap them
- [ ] Composition: Test any
oneOf/anyOfin inputSchema — these now work where they previously failed validation
What If Your Tools Are Already Producing Bad JSON?
The v2 outputSchema enforcement means JSON errors that were previously invisible — truncated responses, single-quoted strings, LLM-generated Python literals — now cause hard validation failures at the MCP layer.
Before migrating to v2, audit your tool outputs: paste any suspicious JSON your tools return into AI JSONMedic to catch and fix malformed output before outputSchema validation surfaces it as an error in production.
FAQ
When does MCP Python SDK v2 stable release?Target is July 27, 2026 — one day before the July 28 spec RC locks. The alpha (2.0.0a1) is on PyPI now. Beta drops June 30 with locked APIs.
Do I need to upgrade Pydantic for MCP v2?Yes, effectively. MCP v2's JSON Schema 2020-12 requirement aligns with Pydantic v2 schema output. Pydantic v1 generates draft-07 schemas with definitions instead of $defs, which break in MCP v2 inputSchema validation. Upgrade to Pydantic v2 before testing against the v2 SDK.
MCP clients will begin dropping 1.x support on the 10-week window after July 28 (by early October 2026). Servers that don't ship v2 support will lose compatibility with major clients. Start migration now — the alpha is stable enough for testing.
How do I handle the error code change if I support both v1 and v2 MCP servers?Catch both codes: match -32602 first (v2 standard), then fall through to -32002 (v1 custom). Most client libraries surface these as McpError with a .code attribute. Check both values until you've fully migrated to v2 servers.
No. FastMCP 2.x registers tools against the v2 protocol. If you need to serve both v1 and v2 clients during a transition, run two server instances. The recommended path is migrate fully to v2 and rely on clients' backward-compat shims.
My tool returns a Pydantic model but I'm getting PydanticSchemaGenerationError. Why?This usually means one of your model fields uses a type Pydantic can't automatically generate a JSON Schema for — a third-party class, a typing.TypeVar, or a custom __get_validators__ v1-style model. Wrap the problematic field in typing.Any temporarily, then replace it with a serializable type. See MCP JSON schema validation errors for common patterns.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles