Skip repeated type conversion and validation in proxy client elicitation handler (#1222)

Co-authored-by: Tapan Chugh <tapanc@cs.washington.edu>
This commit is contained in:
Tapan Chugh 2025-07-22 05:08:39 -07:00 committed by GitHub
commit 1da1eb581a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 5 deletions

View file

@ -566,11 +566,12 @@ class ProxyClient(Client[ClientTransportT]):
A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
"""
ctx = get_context()
result = await ctx.elicit(message, response_type)
if result.action == "accept":
return result.data
else:
return ElicitResult(action=result.action)
result = await ctx.session.elicit(
message=message,
requestedSchema=params.requestedSchema,
related_request_id=ctx.request_id,
)
return ElicitResult(action=result.action, content=result.content)
@classmethod
async def default_log_handler(cls, message: LogMessage) -> None:

View file

@ -4,6 +4,7 @@ from typing import cast
import pytest
from anyio import create_task_group
from mcp.types import LoggingLevel, ModelHint, ModelPreferences, TextContent
from pydantic import BaseModel, Field
from fastmcp import Client, Context, FastMCP
from fastmcp.client.elicitation import ElicitRequestParams, ElicitResult
@ -330,6 +331,50 @@ class TestProxyClient:
assert results["elicitation_a"] == "Hello, Alice!"
assert results["elicitation_b"] == "Hello, Bob!"
async def test_elicit_with_default_values(self, fastmcp_server: FastMCP):
"""
Test that the proxy client correctly handles elicitation with default values (fixes #1167).
"""
@fastmcp_server.tool
async def elicit_with_defaults(context: Context) -> str:
class TestModel(BaseModel):
content: str = Field(description="Your reply content")
acknowledge: bool = Field(
default=False, description="Send immediately or save as draft"
)
result = await context.elicit(
"Please provide input:", response_type=TestModel
)
if result.action == "accept":
return f"Content: {result.data.content}, Acknowledge: {result.data.acknowledge}"
else:
return f"Elicitation {result.action}"
proxy_server = FastMCP.as_proxy(ProxyClient(fastmcp_server))
# Test that elicitation works correctly through the proxy
async def elicitation_handler(
message: str,
response_type: type,
params: ElicitRequestParams,
ctx: RequestContext,
):
# Verify the schema is correct - acknowledge should have default=False, not be nullable
schema = params.requestedSchema
assert schema["properties"]["acknowledge"]["type"] == "boolean"
assert schema["properties"]["acknowledge"]["default"] is False
return {"content": "Test content", "acknowledge": True}
async with Client(
proxy_server, elicitation_handler=elicitation_handler
) as client:
result = await client.call_tool("elicit_with_defaults", {})
assert result.data == "Content: Test content, Acknowledge: True"
async def test_client_factory_creates_fresh_sessions(self, fastmcp_server: FastMCP):
"""Test that the client factory pattern creates fresh sessions for each request."""
from fastmcp.server.proxy import FastMCPProxy