Dropdown for enums
Is your feature request related to a problem? Please describe. I'm always sad when using MCP tools with enum parameters because the Inspector renders them as text input fields despite having the valid options available in the Zod schema. Users must manually type values like "some very long value because I can't provide my actual example because I have a mortgage to pay" without seeing the available options, leading to typos and validation errors. The valid options are only visible in the description text, requiring users to read carefully and type precisely.
Describe the solution you'd like
When the Inspector detects a z.enum() schema for a tool parameter, it should render a <select> dropdown instead of a text input. The dropdown would populate its options from the enum values already available in the Zod schema. For example, z.enum(["some option one", "some option two", "some option three"]) would render as a dropdown with those three options, with any default value pre-selected (possibly even just first in array).
Describe alternatives you've considered
- Autocomplete text input: Add typeahead suggestions based on enum values, but this still requires typing
- Radio buttons: For small enums (< say 5 options), but doesn't scale well for larger lists
- Better inline documentation: Show valid options more prominently near the input, but doesn't prevent typos
A dropdown remains the most intuitive UI pattern for selecting from predefined options and supports me being as lazy as possible.
Additional context Since the Inspector already uses Zod for validation and has access to the enum values via the schema, implementing this should be straightforward. The type information is already there - it just needs to be used for rendering.
Example schema that should trigger dropdown rendering:
z.enum(["option one", "option two", "option three", "option four", "option five", "option six"])
.optional()
.default("option one")
This would benefit all MCP servers using enum parameters, improving user experience and reducing input errors.
Thank you 🤖💕
The fix doesn't work with FastMCP enum parameters
Hi! I've tested the enum dropdown feature from #842 and unfortunately it doesn't work with FastMCP-generated tools. The issue is that FastMCP uses JSON Schema $ref references instead of inline enums.
Minimal reproducible example
from fastmcp import FastMCP
from enum import Enum
mcp = FastMCP("Demo Enum 🚀")
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
@mcp.tool
def set_priority(priority: Priority = Priority.LOW) -> str:
return f"Priority set to: {priority}"
if __name__ == "__main__":
mcp.run(transport="http", port=8005)
The problem
When the Inspector calls tools/list, FastMCP returns this JSON schema:
{
"name": "set_priority",
"inputSchema": {
"type": "object",
"properties": {
"priority": {
"anyOf": [
{
"$ref": "#/$defs/Priority"
},
{
"type": "null"
}
],
"default": "low"
}
},
"$defs": {
"Priority": {
"type": "string",
"enum": ["low", "medium", "high"]
}
}
}
}
The current implementation in ToolsTab.tsx only detects inline enums:
prop.type === "string" && prop.enum
But FastMCP places the enum definition in $defs (to reuse type definitions across multiple parameters) and references it via $ref, so the dropdown is never rendered.
Suggested fix
- Resolve $ref references from $defs or definitions
- Handle refs inside anyOf/oneOf (FastMCP wraps refs in anyOf with null for optional params)
- Extract the enum after resolution
I can submit a PR with the fix if helpful - I've already implemented and tested it locally. All tests pass and it works with both inline enums and FastMCP-style $ref enums.
@olaservo: Hope that helps!
This would benefit all MCP servers using enum parameters, improving user experience and reducing input errors.
Part of the problem is the EnumSchema in the spec. It's wrong.
There is an SEP to improve it and add multi-select in addition to single-select modes.
In the meantime, we have added support for standard enums (without titles).
I don't quite follow what we should be using for enums with the FastMCP server. Are you saying that FastMCP has implemented enums incorrectly? This is the server we're trying to use: https://github.com/Azure-Samples/python-mcp-demo/blob/main/basic_mcp_http.py#L37
The inspector shows those enums as a JSON field right now, and it's unclear what to enter in them.
Is your feature request related to a problem? Please describe. I'm always sad when using MCP tools with enum parameters because the Inspector renders them as text input fields despite having the valid options available in the Zod schema. Users must manually type values like "some very long value because I can't provide my actual example because I have a mortgage to pay" without seeing the available options, leading to typos and validation errors. The valid options are only visible in the description text, requiring users to read carefully and type precisely.
Describe the solution you'd like When the Inspector detects a
z.enum()schema for a tool parameter, it should render a<select>dropdown instead of a text input. The dropdown would populate its options from the enum values already available in the Zod schema. For example,z.enum(["some option one", "some option two", "some option three"])would render as a dropdown with those three options, with any default value pre-selected (possibly even just first in array).Describe alternatives you've considered
- Autocomplete text input: Add typeahead suggestions based on enum values, but this still requires typing
- Radio buttons: For small enums (< say 5 options), but doesn't scale well for larger lists
- Better inline documentation: Show valid options more prominently near the input, but doesn't prevent typos
A dropdown remains the most intuitive UI pattern for selecting from predefined options and supports me being as lazy as possible.
Additional context Since the Inspector already uses Zod for validation and has access to the enum values via the schema, implementing this should be straightforward. The type information is already there - it just needs to be used for rendering.
Example schema that should trigger dropdown rendering:
z.enum(["option one", "option two", "option three", "option four", "option five", "option six"]) .optional() .default("option one") This would benefit all MCP servers using enum parameters, improving user experience and reducing input errors.
Thank you 🤖💕
Ty
I don't quite follow what we should be using for enums with the FastMCP server. Are you saying that FastMCP has implemented enums incorrectly? This is the server we're trying to use: https://github.com/Azure-Samples/python-mcp-demo/blob/main/basic_mcp_http.py#L37
The inspector shows those enums as a JSON field right now, and it's unclear what to enter in them.
Hi @pamelafox!
Not sure what version you're on. Can you try the latest version of the inspector?
npx @modelcontextprotocol/inspector@latest
Below is what I see currently, running the gzip tool of the everything reference server. Notice the tool has an enum for the outputType parameter in its inputSchema, and the UI renders it as a dropdown.
If just updating to the latest version of the inspector doesn't fix your problem:
When you run your server, open up the tools/list as I have in this screenshot, and drill down to your category parameter. Please include a screenshot of the whole Inspector surface with both that parameter shown in the tools/list response and the input field visible, as I have here:
I just ran @latest and am still seeing a text field:
Here's the response from tools/list:
{
"tools": [
{
"name": "add_expense",
"description": "Add a new expense to the expenses.csv file.",
"inputSchema": {
"type": "object",
"properties": {
"date": {
"description": "Date of the expense in YYYY-MM-DD format",
"format": "date",
"type": "string"
},
"amount": {
"description": "Positive numeric amount of the expense",
"type": "number"
},
"category": {
"$ref": "#/$defs/Category",
"description": "Category label"
},
"description": {
"description": "Human-readable description of the expense",
"type": "string"
},
"payment_method": {
"$ref": "#/$defs/PaymentMethod",
"description": "Payment method used"
}
},
"required": [
"date",
"amount",
"category",
"description",
"payment_method"
],
"$defs": {
"Category": {
"enum": [
"food",
"transport",
"entertainment",
"shopping",
"gadget",
"other"
],
"type": "string"
},
"PaymentMethod": {
"enum": [
"amex",
"visa",
"cash"
],
"type": "string"
}
}
},
"_meta": {
"_fastmcp": {
"tags": []
}
}
}
]
}
Looks like the gzip uses a different way of defining enums? Is the $ref way unsupported? Should I file an issue in fastmcp repo?
I also don't understand how to make it work, I have the same issue as @pamelafox
@pamelafox @jjhidalgar-celonis Yep, it looks like the $refs and $defs is what's throwing us here. We do not have support for them in the current implementation. And, IMHO, it would be a bad thing to support. Not saying we won't, but hear me out...
Aren't $defs & $ref valid for JSON schemas?
Absolutely. In a large schema, like the MCP schema, for instance, using $refs and $defs makes sense. That's because you may define (in $defs) an entity that will then be referred to ($ref) multiple times elsewhere in the schema.
For example in the screenshot below, we're referring to an entity definition called Role. A simple search tells us that this entity definition is referred to four times in the schema. That's a net savings over defining Role in each place.
Then why is it bad in a tool schema?
In the small-world of a tool's inputSchema, where are we going to find that savings? If a definition is only referred to one time, as in both of your cases, it is actually a net surplus of tokens, in addition to being more difficult for the model to reason about.
You're extracting the guts of a property definition out to a separate place ($defs), giving it a name, and then referring to it exactly once. In coding, you only extract lines into a function when you're going to call them more than once. The same guidance should apply to schemas.
So what can I do about it if fast-mcp is doing this automagically?
Take care with how you write your schemas. The fast-mcp library has typescript and python implementations. The former uses Zod, the latter Pydandic, and those are the source of the $defs & $ref outputs.
Typescript fast-mcp
The key is to avoid using .refine() or reusing schema definitions, since those trigger the use of $defs and $ref for deduplication. Here are some strategies:
Inline everything directly in the tool definition
import { FastMCP } from 'fast-mcp';
import { z } from 'zod';
const mcp = new FastMCP('my-server');
mcp.tool({
name: 'example_tool',
description: 'Example with inline schemas',
parameters: z.object({
user: z.object({
name: z.string(),
age: z.number().int().positive(),
email: z.string().email()
}),
preferences: z.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean()
})
}),
execute: async ({ user, preferences }) => {
// implementation
}
});
This will generate a schema with all properties defined inline, no $defs.
Avoid schema reuse
If you define a schema once and reuse it multiple times, Zod's JSON Schema generator will automatically extract it into $defs:
// This WILL create $defs
const UserSchema = z.object({ name: z.string() });
mcp.tool({
parameters: z.object({
user1: UserSchema, // Referenced twice
user2: UserSchema // So it goes to $defs
})
});
Instead, inline the definition each time if you want to avoid refs:
// This WON'T create $defs
mcp.tool({
parameters: z.object({
user1: z.object({ name: z.string() }),
user2: z.object({ name: z.string() })
})
});
Python fast-mcp
Inline definitions with Pydantic
The key is to define nested models inline using TypedDict or inline Field() definitions rather than creating separate Pydantic model classes:
from fast_mcp import FastMCP
from pydantic import BaseModel, Field
from typing import Literal
mcp = FastMCP("my-server")
@mcp.tool()
def example_tool(
user_name: str = Field(description="User's name"),
user_age: int = Field(gt=0, description="User's age"),
theme: Literal["light", "dark"] = Field(description="UI theme"),
notifications: bool = Field(description="Enable notifications")
):
"""Example with inline parameter definitions"""
pass
Avoid separate Pydantic models for single-use cases
If you define a separate Pydantic model and use it as a parameter type, it will often get extracted to $defs:
# This WILL likely create $defs
class User(BaseModel):
name: str
age: int
@mcp.tool()
def process_user(user: User):
"""Process a user"""
pass
Use nested inline models sparingly
If you need nested objects, you can sometimes use dictionaries with type hints:
from typing import Dict, Any
@mcp.tool()
def process_data(
config: Dict[str, Any] = Field(
description="Configuration with theme and notifications"
)
):
"""Process with dict instead of nested model"""
pass
However, this loses type validation benefits.
Check Pydantic's schema generation config
Pydantic v2 has a model_config option that can control schema generation. You might be able to set:
class MyModel(BaseModel):
model_config = {
"json_schema_mode": "validation",
"json_schema_serialization_defaults_required": True
}
Though this may not directly control $defs usage.
The Python version is a bit more opinionated about structure since Pydantic is designed around model reuse. If $defs are still appearing with inline definitions, you might need to check fast-mcp's specific implementation or consider flattening your parameter structure to avoid nested objects entirely.
@cliffhall I'm not sure if FastMCP supports other ways of generating enum outputs, I've filed an issue here: https://github.com/jlowin/fastmcp/issues/2236
I'm not sure if FastMCP supports other ways of generating enum outputs, I've filed an issue here: jlowin/fastmcp#2236
@juancarlosm @pamelafox please have another look at my comment above. I was not finished with it and accidentally submitted before finishing the final section on how to write your schemas.
@cliffhall Thanks, thats helpful! I do generally recommend that Python developers use Enum in these situations, because then they can keep using that Enum later in the code, like in if/else statements - that avoids ever relying on string literals. So I think I'll keep my issue in the fastmcp repo, as maybe it'd make sense for them to always avoid refs/defs in their outputs?
I do generally recommend that Python developers use Enum in these situations, because then they can keep using that Enum later in the code, like in if/else statements - that avoids ever relying on string literals.
@pamelafox This is a place where practicality runs up against coding best practices. I agree defining enums for use throughout the code is a good thing. And you could define an enum for use everywhere else in the code but just use Literal in the inputSchema definition. This would suppress the $def $ref that makes the schema larger and more complex than it needs to be and more difficult for a model to reason about, while still allowing you to refer to it elsewhere in your code. It's a tradeoff.
I think I'll keep my issue in the fastmcp repo, as maybe it'd make sense for them to always avoid refs/defs in their outputs?
I commented on your issue. If they can do something to mitigate that would be great, but ultimately its Zod or Pydantic calling the shots on this part of the output I believe.
@olaservo @evalstate I'd appreciate any input you might have on whether the Inspector should support $defs & $refs in tool inputSchemas . My hot take is above.
@cliffhall thank you for your detailed explanation! I really agree with you, the LLM will also benefit as it's easier to understand direct fields than using defs and refs.
Hi @cliffhall @evalstate @KKonstantinov I think we have another example of adding support for $refs here, in this case for elicitations: https://github.com/modelcontextprotocol/inspector/pull/902 and this one? https://github.com/modelcontextprotocol/inspector/pull/901
I agree it seems like its overcomplicating these schema definitions to structure them with defs and refs. It seems like we're debating whether we should support anything that is technically valid, vs things we think are a best practice for structuring inputs?
It seems like we're debating whether we should support anything that is technically valid, vs things we think are a best practice for structuring inputs?
@olaservo Yes, that was my hot take. But after absolutely leaning into, I think there's no getting around it. The creation of the actual schema that's sent to the client is usually a bit removed from the process of defining the inputs and fast-mcp is absolutely leaning into outputting $defs/refs. So we might as well support changes to handle $refs and look into adding support for $defs.
To that end https://github.com/modelcontextprotocol/inspector/pull/889 was just merged last week which handles $refs in /properties.