From 9975e1cc6eeef6465604626df4d08eaa28e79902 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:07:30 -0500 Subject: [PATCH] Document dict-to-Message prompt migration in v2 upgrade guide (#3225) --- .../upgrading/from-fastmcp-2.mdx | 24 +++++++++++++++++++ .../upgrading/from-mcp-sdk.mdx | 6 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/upgrading/from-fastmcp-2.mdx b/docs/getting-started/upgrading/from-fastmcp-2.mdx index 2bd544f09..ae3726914 100644 --- a/docs/getting-started/upgrading/from-fastmcp-2.mdx +++ b/docs/getting-started/upgrading/from-fastmcp-2.mdx @@ -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: diff --git a/docs/getting-started/upgrading/from-mcp-sdk.mdx b/docs/getting-started/upgrading/from-mcp-sdk.mdx index ddf9c9c81..4ec319baf 100644 --- a/docs/getting-started/upgrading/from-mcp-sdk.mdx +++ b/docs/getting-started/upgrading/from-mcp-sdk.mdx @@ -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