Document dict-to-Message prompt migration in v2 upgrade guide (#3225)

This commit is contained in:
Jeremiah Lowin 2026-02-18 19:07:30 -05:00 committed by GitHub
commit 9975e1cc6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 2 deletions

View file

@ -62,6 +62,8 @@ BREAKING CHANGES (will crash at import or runtime):
5. PROMPTS: mcp.types.PromptMessage replaced by fastmcp.prompts.Message.
Before: PromptMessage(role="user", content=TextContent(type="text", text="Hello"))
After: Message("Hello") # role defaults to "user", accepts plain strings
Also: if prompts return raw dicts like {"role": "user", "content": "..."}, these must become Message objects.
v2 silently coerced dicts; v3 requires typed Message objects or plain strings.
6. AUTH PROVIDERS: No longer auto-load from env vars. Pass client_id, client_secret explicitly via os.environ.
@ -180,6 +182,28 @@ def my_prompt() -> Message:
return Message("Hello")
```
If your prompt functions return raw dicts with `role` and `content` keys, those also need to change. v2 silently coerced dicts into prompt messages, but v3 requires typed `Message` objects (or plain strings for single user messages):
```python
# Before (v2 accepted this)
@mcp.prompt
def my_prompt():
return [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "How can I help?"},
]
# After
from fastmcp.prompts import Message
@mcp.prompt
def my_prompt() -> list[Message]:
return [
Message("Hello"),
Message("How can I help?", role="assistant"),
]
```
**Context state methods are async**
`ctx.set_state()` and `ctx.get_state()` are now async because state in v3 is session-scoped and backed by a pluggable storage backend (rather than a simple dict). This means state persists across multiple tool calls within the same session:

View file

@ -44,10 +44,12 @@ Fix: pass these to run() instead.
Before: `mcp = FastMCP("server", host="0.0.0.0", port=8080); mcp.run()`
After: `mcp = FastMCP("server"); mcp.run(transport="http", host="0.0.0.0", port=8080)`
STEP 3 — PROMPTS (only if using PromptMessage directly):
STEP 3 — PROMPTS (only if using PromptMessage directly or returning dicts):
mcp.types.PromptMessage is replaced by fastmcp.prompts.Message.
Before: `PromptMessage(role="user", content=TextContent(type="text", text="Hello"))`
After: `Message("Hello")` — role defaults to "user", accepts plain strings.
Also: if prompts return raw dicts like {"role": "user", "content": "..."}, these must become Message objects or plain strings.
The MCP SDK's FastMCP 1.0 silently coerced dicts; standalone FastMCP requires typed returns.
STEP 4 — OTHER MCP IMPORTS (only if importing from mcp.* directly):
Direct imports from the `mcp` package (e.g., `import mcp.types`, `from mcp.server.stdio import stdio_server`) still work because FastMCP includes `mcp` as a dependency. However, prefer FastMCP's own APIs where equivalents exist:
@ -83,7 +85,7 @@ If you pass the old kwargs, you'll get a clear `TypeError` with a migration hint
### Prompts
If your prompt functions return `mcp.types.PromptMessage` objects, you can upgrade to FastMCP's simpler `Message` class. Or just return a plain string — it's automatically wrapped as a user message:
If your prompt functions return `mcp.types.PromptMessage` objects or raw dicts with `role`/`content` keys, you'll need to upgrade to FastMCP's `Message` class. Or just return a plain string — it's automatically wrapped as a user message. The MCP SDK's bundled FastMCP 1.0 silently coerced dicts into messages; standalone FastMCP requires typed `Message` objects or strings.
```python
from fastmcp import FastMCP