Skip to content

Add support for Elicitation #625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Add support for Elicitation #625

wants to merge 13 commits into from

Conversation

ihrpr
Copy link
Contributor

@ihrpr ihrpr commented May 4, 2025

Elicitation Spec PR

Overview

Adding support for the elicitation feature, allowing servers to interactively request additional information from clients during tool execution. The implementation follows a layered approach with both low-level protocol support and a high-level API through FastMCP.

Key Changes

1. Protocol Layer (src/mcp/types.py)

  • Added ElicitRequest, ElicitRequestParams, and ElicitResult types to support the elicitation protocol
  • Extended ServerRequest and ClientResult to handle elicitation messages
  • Follows the MCP specification for elicitation support

2. Low-Level Server/Client (src/mcp/server/session.py, src/mcp/client/session.py)

  • Added elicit() method to ServerSession for sending elicitation requests
  • Added elicit() method to ClientSession for handling server elicitation requests
  • Design decision: Uses raw dictionaries instead of Pydantic models - this maintains consistency with the low-level API design which provides direct protocol access without data modeling abstractions

3. FastMCP High-Level API (src/mcp/server/fastmcp/server.py)

  • Added Context.elicit() method that returns an ElicitationResult object
  • Design decision: Returns a result object instead of raising exceptions for decline/cancel actions. This treats user declining or cancelling as expected outcomes rather than error conditions
  • Enforces MCP specification requirement that elicitation schemas only contain primitive types (str, int, float, bool)
  • Validates schemas at runtime with descriptive error messages

FastMCP Interface Design

The FastMCP elicitation interface prioritizes type safety and explicit result handling:

@server.tool()
async def book_table(date: str, party_size: int, ctx: Context) -> str:
    class ConfirmBooking(BaseModel):
        confirm: bool = Field(description="Confirm booking?")
        notes: str = Field(default="", description="Special requests")

    result = await ctx.elicit(
        message=f"Confirm booking for {party_size} on {date}?",
        schema=ConfirmBooking
    )

    if result.action == "accept" and result.data:
        # result.data is typed as ConfirmBooking
        if result.data.confirm:
            return f"Booked! Notes: {result.data.notes}"
    elif result.action == "decline":
        return "User declined"
    else:  # cancel
        return "User cancelled"

Interface characteristics:

  • result.data is typed according to the provided schema
  • All three user actions (accept/decline/cancel) are handled explicitly
  • Validation errors are captured in result.validation_error when user input doesn't match schema
  • Schema validation happens before sending the request, failing fast if non-primitive types are used

Comment on lines 1008 to 1009
requestedSchema: dict[str, Any],
) -> dict[str, Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you want would be something like this:

async def elicit(self, schema: type[T], message: str | None = None) -> T: ...

And then it can be used inside the tools as:

from mcp.server.fastmcp import FastMCP
from typing_extensions import TypedDict

app = FastMCP()

class MyServerSchema(TypedDict):
    name: str

@app.tool()
async def potato_tool(ctx: Context):
    content = await ctx.elicit(MyServerSchema)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added message as optional because I can imagine us extracting the message from the docstring of MyServerSchema. For the best user experience.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I,plemented elicit, looks nice and typesafa now!


Re message in the schema:

The message and schema serve distinct purposes in the elicitation flow:

  • The message is the contextual question or prompt that explains why we're asking for information (e.g., "No tables available at 7 PM. Would you like to try 8 PM instead?")
  • The schema defines what information we're collecting and its structure (e.g., fields for confirmation, alternative time, etc.)

This separation allows for:

  1. Dynamic context - The same schema can be reused with different messages based on the situation
  2. Clearer intent - The message can provide specific context that wouldn't make sense as a docstring (like "Your session will expire in 5 minutes. Save your work?")

@ihrpr ihrpr changed the title Elicitation prototype Add support for Elicitation Jun 12, 2025
@ihrpr ihrpr requested review from dsp-ant and bhosmer-ant June 12, 2025 10:16
@ihrpr ihrpr marked this pull request as ready for review June 12, 2025 10:17
@ihrpr ihrpr linked an issue Jun 12, 2025 that may be closed by this pull request
Comment on lines +335 to +337
result = await ctx.elicit(
message=f"Confirm booking for {party_size} on {date}?", schema=ConfirmBooking
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it gives us a better API and user experience if ctx.elicit(schema=SchemaT) always return an instance of SchemaT, or an exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment implies that an exception would be raised if user rejects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce elicitation as new client capability
2 participants