Deprecate ctx.elicit() without response_type (#3916)

This commit is contained in:
Jeremiah Lowin 2026-04-13 21:23:31 -04:00 committed by GitHub
commit f4f2ec07fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 3 deletions

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import logging
import warnings
import weakref
from collections.abc import Callable, Generator, Mapping, Sequence
from contextlib import contextmanager
@ -26,6 +27,8 @@ from starlette.requests import Request
from typing_extensions import TypeVar
from uncalled_for import SharedContext
import fastmcp
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.resources.base import ResourceResult
from fastmcp.server.elicitation import (
AcceptedElicitation,
@ -1130,9 +1133,11 @@ class Context:
"value" field will be generated for the MCP interaction and
automatically deconstructed into the primitive type upon response.
If the response_type is None, the generated schema will be that of an
empty object in order to comply with the MCP protocol requirements.
Clients must send an empty object ("{}")in response.
Passing ``response_type=None`` (or omitting it) is deprecated and will
be removed in a future version. The resulting empty-schema form-mode
request is ambiguous and causes some clients (e.g. VS Code) to hang on
an empty form. Pass an explicit ``response_type`` describing the data
you want back.
Args:
message: A human-readable message explaining what information is needed
@ -1153,6 +1158,17 @@ class Context:
contexts. In background task mode (SEP-1686), it will set the task
status to "input_required" and wait for the client to provide input.
"""
if response_type is None and fastmcp.settings.deprecation_warnings:
warnings.warn(
"Calling ctx.elicit() without a response_type is deprecated "
"and will be removed in a future version. The empty-schema "
"form-mode request is ambiguous under the current MCP spec "
"and causes some clients (e.g. VS Code) to render an empty, "
"non-functional form. Pass an explicit response_type "
"describing the data you expect back.",
FastMCPDeprecationWarning,
stacklevel=2,
)
config = parse_elicit_response_type(
response_type,
response_title=response_title,

View file

@ -0,0 +1,29 @@
"""Tests for deprecated elicitation behavior."""
from typing import Any, cast
import pytest
from fastmcp import Context, FastMCP
from fastmcp.client.client import Client
from fastmcp.client.elicitation import ElicitResult
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.server.elicitation import AcceptedElicitation
async def test_elicitation_none_response_type_warns_deprecation():
"""Passing response_type=None is deprecated — warn at call time."""
mcp = FastMCP("TestServer")
@mcp.tool
async def my_tool(context: Context) -> dict[str, Any]:
with pytest.warns(FastMCPDeprecationWarning, match="response_type"):
result = await context.elicit(message="", response_type=None)
assert isinstance(result, AcceptedElicitation)
return cast(dict[str, Any], result.data)
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={})
async with Client(mcp, elicitation_handler=elicitation_handler) as client:
await client.call_tool("my_tool", {})