From 1da1eb581a58b1b7cdddd49e39a29ca9e9b83ea3 Mon Sep 17 00:00:00 2001 From: Tapan Chugh Date: Tue, 22 Jul 2025 05:08:39 -0700 Subject: [PATCH] Skip repeated type conversion and validation in proxy client elicitation handler (#1222) Co-authored-by: Tapan Chugh --- src/fastmcp/server/proxy.py | 11 +++--- tests/server/proxy/test_proxy_client.py | 45 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/fastmcp/server/proxy.py b/src/fastmcp/server/proxy.py index cfd8ba57a..ebcb0f80e 100644 --- a/src/fastmcp/server/proxy.py +++ b/src/fastmcp/server/proxy.py @@ -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: diff --git a/tests/server/proxy/test_proxy_client.py b/tests/server/proxy/test_proxy_client.py index ffd17116f..6239a9e85 100644 --- a/tests/server/proxy/test_proxy_client.py +++ b/tests/server/proxy/test_proxy_client.py @@ -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