Skip to content

fix: Support string enum for elicitation - #1563

Closed
vincent0426 wants to merge 4 commits into
modelcontextprotocol:mainfrom
vincent0426:fix/elicitation-str-enum
Closed

fix: Support string enum for elicitation#1563
vincent0426 wants to merge 4 commits into
modelcontextprotocol:mainfrom
vincent0426:fix/elicitation-str-enum

Conversation

@vincent0426

Copy link
Copy Markdown
Contributor

Add support for string Enum types in elicitation schemas

Related PR: modelcontextprotocol/inspector#902

Changes Made

  • Added StrEnum to _ELICITATION_PRIMITIVE_TYPES tuple
  • Enhanced _is_primitive_field() to detect string enum types using issubclass() checks

Motivation and Context

Per https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#supported-schema-types, elicitation schemas support enum types.

Currently, python-sdk only supported basic primitive types (str, int, float, bool) in elicitation schemas, but did not recognize Enum types.

How Has This Been Tested?

  • Added test cases in test_elicitation_schema_validation() to verify enum schemas

  • Manual test with

fromenumimportEnum, StrEnumfrompydanticimportBaseModel, Fieldfrommcp.server.fastmcpimportContext, FastMCPfrommcp.server.sessionimportServerSessionmcp=FastMCP(name="Elicitation Example")
classFruitEnum(str, Enum):
pear="pear"apple="apple"classTestStrEnum(StrEnum):
one="1"two="2"three="3"classBookingPreferences(BaseModel):
"""Schema for collecting user preferences."""checkAlternative: bool=Field(description="Would you like to check another date?")
alternativeDate: str=Field(
default="2024-12-26",
description="Alternative date (YYYY-MM-DD)",
)
fruit: FruitEnum=FruitEnum.pearstrenum: TestStrEnum=TestStrEnum.one@mcp.tool()asyncdefbook_table(date: str, time: str, party_size: int, ctx: Context[ServerSession, None]) ->str:
"""Book a table with date availability check."""# Check if date is availableifdate=="2024-12-25":
# Date unavailable - ask user for alternativeresult=awaitctx.elicit(
message=(f"No tables available for {party_size} on {date}. Would you like to try another date?"),
schema=BookingPreferences,
)
print(result)
ifresult.action=="accept"andresult.data:
ifresult.data.checkAlternative:
returnf"[SUCCESS] Booked for {result.data.alternativeDate}"return"[CANCELLED] No booking made"return"[CANCELLED] Booking cancelled"# Date availablereturnf"[SUCCESS] Booked for {date} at {time}"@mcp.tool()asyncdefget_uuid(ctx: Context[ServerSession, None]) ->str:
"""Get a unique identifier."""result=awaitctx.elicit(
message="Please enter a unique identifier",
schema=InvalidFormatSchema,
)
ifresult.action=="accept"andresult.data:
returnf"[SUCCESS] Unique identifier: {result.data.id}"return"[CANCELLED] No unique identifier provided"if__name__=="__main__":
mcp.run(transport="streamable-http")

Results

Screenshot 2025-11-01 at 10 36 00 PMScreenshot 2025-11-01 at 10 36 02 PM

Breaking Changes

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The documentation doesn't explicitly specify that enums must be string enums. Should we add that clarification since both the TypeScript and Rust SDKs only support string enums?

https://github.com/modelcontextprotocol/typescript-sdk/blob/783d53be1049041cc1f0a23c070c3400208a94ff/src/types.ts#L1221-L1229

https://github.com/modelcontextprotocol/rust-sdk/blob/04e0590652d029e2cdfadee6f4c7255280dfba0e/crates/rmcp/src/model/elicitation_schema.rs#L479-L481

@vincent0426
vincent0426force-pushed the fix/elicitation-str-enum branch from a44e8b3 to 07bef73CompareNovember 2, 2025 06:06
@maxisbeymaxisbey added spec P2 Moderate issues affecting some users, edge cases, potentially valuable feature bug Something isn't working labels Nov 4, 2025
@felixweinbergerfelixweinberger added improves spec compliance When a change improves ability of SDK users to comply with spec definition and removed spec labels Nov 12, 2025
@maxisbey

Copy link
Copy Markdown
Contributor

Hey sorry it took so long to get to this! Would you still be interested in updating this to the latest from main? There's been quite a lot changed about elicitations since this PR landed.

@vincent0426
vincent0426force-pushed the fix/elicitation-str-enum branch from 07bef73 to 97b156dCompareDecember 19, 2025 18:08
@maxisbey

Copy link
Copy Markdown
Contributor

Hey! Thanks for putting this together. I took a closer look at where things stand now, and wanted to share what I found.

Since this PR was opened, SEP-1330 landed via PR #1246 (merged Nov 23), which added enum support but took a different approach - using json_schema_extra on str or list[str] fields:

classFavoriteColorSchema(BaseModel):
favorite_color: str=Field(
description="Select your favorite color",
json_schema_extra={
"oneOf": [
{"const": "red", "title": "Red"},
{"const": "green", "title": "Green"},
]
},
)

The tricky thing with native Python enums is that Pydantic generates $ref/$defs in the JSON schema output, which some clients might not handle well - the spec mentions that schemas should be "flat objects with primitive properties only" and that "advanced JSON Schema features are intentionally not supported."

That said, native enum support could still be valuable from a DX perspective - it's more Pythonic and gives you better type checking. It would just need some extra work to inline the $defs before sending to clients.

Given all this, I'm thinking we close this PR and instead open an issue for implementing native Python enum support properly - @vincent0426 what do you think?

AI Disclaimer

MukundaKatta added a commit to MukundaKatta/python-sdk that referenced this pull request Apr 15, 2026
…textprotocol#2384)
Pydantic's model_json_schema() emits $ref/$defs for nested models, but LLM
clients consuming tools/list often can't resolve $ref and serialize referenced
parameters as stringified JSON instead of structured objects.
Inline local $ref pointers in tool schemas so they're self-contained and
LLM-consumable, matching behavior in typescript-sdk (modelcontextprotocol#1563) and go-sdk.
Behavior:
- Caches resolved defs (diamond references resolve each def once)
- Cycles left in place with $defs entries preserved (degraded but correct)
- Sibling keywords alongside $ref preserved per JSON Schema 2020-12
- External $ref and unknown local refs untouched
- Input schema not mutated
Changes:
- Add mcp/server/mcpserver/utilities/schema.py with dereference_local_refs()
- Apply to tool inputSchema at Tool.from_function()
- 11 unit tests covering simple refs, diamond, cycles, siblings, legacy
'definitions' keyword, external refs, unknown refs, nested arrays/objects,
input immutability
Closesmodelcontextprotocol#2384
MukundaKatta added a commit to MukundaKatta/python-sdk that referenced this pull request Apr 15, 2026
…textprotocol#2384)
Pydantic's model_json_schema() emits $ref/$defs for nested models, but LLM
clients consuming tools/list often cannot resolve $ref and serialize
referenced parameters as stringified JSON instead of structured objects.
Inline local $ref pointers in tool schemas so they're self-contained
and LLM-consumable, matching behavior in typescript-sdk (modelcontextprotocol#1563) and go-sdk.
Behavior:
- Caches resolved defs (diamond references resolve each def once)
- Cycles left in place with $defs entries preserved (degraded but correct)
- Sibling keywords alongside $ref preserved per JSON Schema 2020-12
- External $ref and unknown local refs untouched
- Input schema not mutated
Changes:
- Add mcp/server/mcpserver/utilities/schema.py with dereference_local_refs()
- Apply to tool inputSchema at Tool.from_function()
- 11 unit tests covering simple refs, diamond, cycles, siblings, legacy
'definitions' keyword, external refs, unknown refs, nested arrays/objects,
input immutability
Closesmodelcontextprotocol#2384
Signed-off-by: Mukunda Katta <mukunda.vjcs6@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugSomething isn't workingimproves spec complianceWhen a change improves ability of SDK users to comply with spec definitionP2Moderate issues affecting some users, edge cases, potentially valuable feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@vincent0426@maxisbey@felixweinberger